Documentation

diagram2code converts Mermaid ER and PlantUML entity-relationship diagrams into production-ready SQL DDL for PostgreSQL, MySQL, SQLite, and Oracle 19c+.

🧜 Mermaid ER

Parse erDiagram blocks with tables, columns, types, and relationships.

🌱 PlantUML

Parse @startuml entity diagrams with stereotypes and cardinalities.

🐘 PostgreSQL

SERIAL PKs, UUID, CREATE TYPE for enums, COMMENT ON syntax.

🐬 MySQL

AUTO_INCREMENT, backtick quoting, inline ENUM(), COMMENT inline.

πŸͺΆ SQLite

Simplified type affinity (TEXT, INTEGER, REAL, BLOB), IF NOT EXISTS.

πŸ›οΈ Oracle 19c+

IDENTITY columns, NUMBER, VARCHAR2, COMMENT ON, no IF NOT EXISTS.

How It Works

The pipeline has three stages:

  1. Parse β€” The diagram text (Mermaid or PlantUML) is lexed and parsed into an Intermediate Schema Model (tables, columns, relationships).
  2. Normalize β€” The schema is cleaned: tables without a primary key get a synthetic id column, duplicate columns are removed, and warnings are emitted.
  3. Generate β€” The normalized schema is emitted as SQL DDL for the chosen database dialect, with dialect-specific type mapping, quoting, constraints, and comments.
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Mermaid ER   │────▢│   Intermediate   │────▢│  PostgreSQL DDL  β”‚
β”‚  PlantUML     β”‚     β”‚  Schema Model    β”‚     β”‚  MySQL DDL       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚  SQLite DDL      β”‚
                                              β”‚  Oracle DDL      β”‚
                                              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Quick Start Example

Here's a simple diagram and the SQL it generates for each database:

Mermaid Input
erDiagram
    USER {
        int id PK
        string name
        string email
    }
    ORDER {
        int id PK
        int user_id FK
        decimal total
    }
    USER ||--o{ ORDER : places
PostgreSQL Output
CREATE TABLE "user" (
    "id" SERIAL PRIMARY KEY,
    "name" TEXT,
    "email" TEXT
);

CREATE TABLE "order" (
    "id" SERIAL PRIMARY KEY,
    "user_id" INTEGER,
    "total" NUMERIC
);

ALTER TABLE "order"
    ADD CONSTRAINT "fk_order_user_id"
    FOREIGN KEY ("user_id") REFERENCES "user"("id");

Mermaid ER Syntax

Mermaid ER diagrams begin with erDiagram. Tables are defined as blocks with columns, and relationships connect tables with cardinality notation.

Table & Column Syntax

erDiagram
    TABLE_NAME {
        type column_name MARKERS "optional comment"
    }

Each column line has:

Mermaid β€” Full-featured table
erDiagram
    PRODUCT {
        int id PK "Primary key"
        string name "Product name"
        decimal price
        boolean active
        string sku UK "Stock keeping unit"
        int category_id FK
        timestamp created_at
    }

PlantUML Syntax

PlantUML diagrams are wrapped in @startuml / @enduml. Entities use the entity keyword, columns use name : type order (note: reversed from Mermaid), and stereotypes mark PK/FK/UNIQUE.

PlantUML Input
@startuml

entity User {
  * id : int <<PK>>
  name : string
  email : string <<UNIQUE>>
}

entity Order {
  * id : int <<PK>>
  user_id : int <<FK>>
  total : decimal
}

User ||--o{ Order : places

@enduml
Key Differences from Mermaid
  • Wrapped in @startuml / @enduml
  • Column format: name : type (name first)
  • Required columns: prefix with *
  • Markers use stereotypes: <<PK>>, <<FK>>, <<UNIQUE>>
  • Entity names are PascalCase (converted to snake_case in SQL)

Column Markers

Markers control constraints generated on each column:

MarkerMermaidPlantUMLEffect
Primary KeyPK<<PK>>Column becomes PRIMARY KEY. If single PK + int type β†’ auto-increment.
Foreign KeyFK<<FK>>Column participates in ALTER TABLE ... FOREIGN KEY from relationships.
UniqueUK<<UNIQUE>>Adds UNIQUE constraint on the column.
Not Null(implicit for PK)* prefixAdds NOT NULL to the column definition.

Relationships

Relationships define foreign key constraints between tables. Both Mermaid and PlantUML use similar cardinality notation:

NotationMeaningSQL Result
||--||One to oneFK + UNIQUE constraint
||--o{One to manyFK on the "many" side
}o--||Many to oneFK on the "many" side
}o--o{Many to manyJunction table (future)
Relationship β†’ Foreign Key
-- From this Mermaid relationship:
USER ||--o{ ORDER : places

-- diagram2code generates:
ALTER TABLE "order"
    ADD CONSTRAINT "fk_order_user_id"
    FOREIGN KEY ("user_id") REFERENCES "user"("id");
Convention: The FK constraint name is auto-generated as fk_{table}_{column}. The FK column is matched by looking for columns named {referenced_table}_id on the "many" side.

PostgreSQL postgres

The PostgreSQL generator produces idiomatic PG DDL:

PostgreSQL β€” Comments & Types
CREATE TABLE "product" (
    "id" SERIAL PRIMARY KEY,
    "name" TEXT NOT NULL,
    "price" NUMERIC,
    "active" BOOLEAN DEFAULT true,
    "created_at" TIMESTAMPTZ
);

COMMENT ON COLUMN "product"."name" IS 'The display name of the product';

MySQL mysql

The MySQL generator produces idiomatic MySQL DDL:

MySQL β€” Enums & Comments
CREATE TABLE `order` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `status` ENUM('pending', 'shipped', 'delivered') COMMENT 'Order status',
    `total` DECIMAL,
    `user_id` INT
);

ALTER TABLE `order`
    ADD CONSTRAINT `fk_order_user_id`
    FOREIGN KEY (`user_id`) REFERENCES `user`(`id`);

SQLite sqlite

SQLite uses a simplified type system with type affinity:

SQLite β€” Simplified Types
CREATE TABLE "product" (
    "id" INTEGER PRIMARY KEY,
    "name" TEXT,
    "price" REAL,
    "active" INTEGER,
    "created_at" TEXT
);

-- Note: No COMMENT syntax, no ENUM, no UUID.
-- boolean β†’ INTEGER, decimal β†’ REAL, timestamp β†’ TEXT.

Oracle 19c+ oracle

The Oracle generator targets Oracle Database 19c and later:

Oracle 19c+ β€” Identity & Types
CREATE TABLE "product" (
    "id" NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    "name" VARCHAR2(255) NOT NULL,
    "price" NUMBER,
    "active" NUMBER(1),
    "created_at" TIMESTAMP
);

COMMENT ON COLUMN "product"."name" IS 'The display name of the product';
Oracle note: Oracle does not support CREATE TABLE IF NOT EXISTS. Running the generated DDL twice will produce an error. Use PL/SQL exception handling or check ALL_TABLES before creating.

Abstract Type Mapping

Abstract types are the recommended portable types. They map to the best native type for each database:

Abstract TypePostgreSQLMySQLSQLiteOracle
intINTEGERINTINTEGERNUMBER
stringTEXTVARCHAR(255)TEXTVARCHAR2(255)
decimalNUMERICDECIMALREALNUMBER
booleanBOOLEANTINYINT(1)INTEGERNUMBER(1)
dateDATEDATETEXTDATE
timestampTIMESTAMPTZDATETIMETEXTTIMESTAMP
uuidUUIDCHAR(36)TEXTRAW(16)
textTEXTTEXTTEXTCLOB
enumCREATE TYPEENUM(...)TEXTVARCHAR2(255)

Native / Pass-through Types

You can also use native database types directly in your diagrams. They are mapped to the closest equivalent in each dialect:

Native TypePostgreSQLMySQLSQLiteOracle
bigintBIGINTBIGINTINTEGERNUMBER(19)
smallintSMALLINTSMALLINTINTEGERNUMBER(5)
floatDOUBLE PRECISIONFLOATREALBINARY_DOUBLE
doubleDOUBLE PRECISIONDOUBLEREALBINARY_DOUBLE
realREALREALREALBINARY_FLOAT
varcharVARCHARVARCHARTEXTVARCHAR2
varchar2VARCHARVARCHARTEXTVARCHAR2
charCHARCHARTEXTCHAR
clobTEXTLONGTEXTTEXTCLOB
blobBYTEABLOBBLOBBLOB
serialSERIALINT AUTO_INCREMENTINTEGERNUMBER
datetimeTIMESTAMPTZDATETIMETEXTTIMESTAMP
boolBOOLEANTINYINT(1)INTEGERNUMBER(1)

Parameterized Types

Types with size or precision parameters like varchar2(4000) or number(10,2) are preserved in dialects that support them:

InputPostgreSQLMySQLSQLiteOracle
varchar2(4000)VARCHAR(4000)VARCHAR(4000)TEXTVARCHAR2(4000)
varchar(100)VARCHAR(100)VARCHAR(100)TEXTVARCHAR2(100)
number(10,2)NUMERIC(10,2)NUMERIC(10,2)NUMERICNUMBER(10,2)
char(36)CHAR(36)CHAR(36)TEXTCHAR(36)
SQLite note: SQLite ignores size parameters. All string types become TEXT, and numeric types are reduced to NUMERIC, INTEGER, or REAL based on type affinity.

Unknown Types

If you use a type not in the mapping tables (e.g. jsonb, geometry, citext), it is passed through as-is in UPPERCASE:

-- Input: my_custom_type col_name
-- Output (all dialects):
"col_name" MY_CUSTOM_TYPE

This means you can use database-specific types even if they aren't in the built-in mapping. They will work for your target dialect and pass through unchanged for others.

Comments

Column comments from the diagram are emitted using dialect-specific syntax:

DialectComment Syntax
PostgreSQLCOMMENT ON COLUMN "table"."col" IS 'text';
MySQL"col" INT COMMENT 'text' (inline)
SQLiteNot supported β€” comments are omitted
OracleCOMMENT ON COLUMN "table"."col" IS 'text';
Mermaid with comments
erDiagram
    USER {
        int id PK "Primary identifier"
        string email UK "Login email"
    }
PostgreSQL
CREATE TABLE "user" (
    "id" SERIAL PRIMARY KEY,
    "email" TEXT NOT NULL UNIQUE
);

COMMENT ON COLUMN "user"."id"
    IS 'Primary identifier';
COMMENT ON COLUMN "user"."email"
    IS 'Login email';

Enums

Enum handling varies significantly by dialect:

DialectEnum Strategy
PostgreSQLSeparate CREATE TYPE "table_col_enum" AS ENUM('a','b','c'); before the table. Column type references the created type.
MySQLInline ENUM('a','b','c') directly in the column definition.
SQLiteNo enum support β€” falls back to TEXT.
OracleNo native enum β€” falls back to VARCHAR2(255). Consider adding a CHECK constraint manually.

Constraints & Indexes

The following constraints are generated from diagram markers:

Default Values

Default values are emitted with automatic quoting: numeric values are unquoted, string values are single-quoted.

-- Numeric default:    DEFAULT 0
-- String default:     DEFAULT 'active'
-- Boolean default:    DEFAULT true  (postgres) / DEFAULT 1 (mysql/sqlite/oracle)

Schema Normalization

Before generating SQL, the schema goes through normalization:

Tip: Check warnings in the CLI (stderr) or API response (warnings array) to catch schema issues early.

Full Example: E-Commerce Schema

Mermaid Input
erDiagram
    CUSTOMER {
        int id PK
        string name
        string email UK
        timestamp created_at
    }
    PRODUCT {
        int id PK
        string title
        decimal price
        text description
        boolean in_stock
    }
    ORDER {
        int id PK
        int customer_id FK
        timestamp ordered_at
        decimal total
    }
    ORDER_ITEM {
        int id PK
        int order_id FK
        int product_id FK
        int quantity
        decimal unit_price
    }
    CUSTOMER ||--o{ ORDER : places
    ORDER ||--|{ ORDER_ITEM : contains
    PRODUCT ||--o{ ORDER_ITEM : "listed in"
MySQL Output
CREATE TABLE `customer` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `name` VARCHAR(255),
    `email` VARCHAR(255) NOT NULL UNIQUE,
    `created_at` DATETIME
);

CREATE TABLE `product` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `title` VARCHAR(255),
    `price` DECIMAL,
    `description` TEXT,
    `in_stock` TINYINT(1)
);

CREATE TABLE `order` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `customer_id` INT,
    `ordered_at` DATETIME,
    `total` DECIMAL
);

CREATE TABLE `order_item` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `order_id` INT,
    `product_id` INT,
    `quantity` INT,
    `unit_price` DECIMAL
);

ALTER TABLE `order`
    ADD CONSTRAINT `fk_order_customer_id`
    FOREIGN KEY (`customer_id`)
    REFERENCES `customer`(`id`);

ALTER TABLE `order_item`
    ADD CONSTRAINT `fk_order_item_order_id`
    FOREIGN KEY (`order_id`)
    REFERENCES `order`(`id`);

ALTER TABLE `order_item`
    ADD CONSTRAINT `fk_order_item_product_id`
    FOREIGN KEY (`product_id`)
    REFERENCES `product`(`id`);

Full Example: Blog Platform

PlantUML Input
@startuml

entity Author {
  * id : int <<PK>>
  name : string
  email : string <<UNIQUE>>
  bio : text
}

entity Post {
  * id : int <<PK>>
  author_id : int <<FK>>
  title : string
  body : text
  published : boolean
  created_at : timestamp
}

entity Tag {
  * id : int <<PK>>
  name : string <<UNIQUE>>
}

Author ||--o{ Post : writes

@enduml
Oracle Output
CREATE TABLE "author" (
    "id" NUMBER GENERATED ALWAYS AS IDENTITY
        PRIMARY KEY,
    "name" VARCHAR2(255),
    "email" VARCHAR2(255) NOT NULL UNIQUE,
    "bio" CLOB
);

CREATE TABLE "post" (
    "id" NUMBER GENERATED ALWAYS AS IDENTITY
        PRIMARY KEY,
    "author_id" NUMBER,
    "title" VARCHAR2(255),
    "body" CLOB,
    "published" NUMBER(1),
    "created_at" TIMESTAMP
);

CREATE TABLE "tag" (
    "id" NUMBER GENERATED ALWAYS AS IDENTITY
        PRIMARY KEY,
    "name" VARCHAR2(255) NOT NULL UNIQUE
);

ALTER TABLE "post"
    ADD CONSTRAINT "fk_post_author_id"
    FOREIGN KEY ("author_id")
    REFERENCES "author"("id");

Same Diagram β†’ All 4 Dialects

Here's how the same simple diagram renders across all supported databases:

Input (Mermaid)
erDiagram
    USER {
        int id PK
        string name
        string email
    }
    ORDER {
        int id PK
        int user_id FK
        decimal total
    }
    USER ||--o{ ORDER : places
PostgreSQL
CREATE TABLE "user" (
    "id" SERIAL PRIMARY KEY,
    "name" TEXT,
    "email" TEXT
);

CREATE TABLE "order" (
    "id" SERIAL PRIMARY KEY,
    "user_id" INTEGER,
    "total" NUMERIC
);

ALTER TABLE "order"
    ADD CONSTRAINT "fk_order_user_id"
    FOREIGN KEY ("user_id")
    REFERENCES "user"("id");
MySQL
CREATE TABLE `user` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `name` VARCHAR(255),
    `email` VARCHAR(255)
);

CREATE TABLE `order` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `user_id` INT,
    `total` DECIMAL
);

ALTER TABLE `order`
    ADD CONSTRAINT `fk_order_user_id`
    FOREIGN KEY (`user_id`)
    REFERENCES `user`(`id`);
SQLite
CREATE TABLE "user" (
    "id" INTEGER PRIMARY KEY,
    "name" TEXT,
    "email" TEXT
);

CREATE TABLE "order" (
    "id" INTEGER PRIMARY KEY,
    "user_id" INTEGER,
    "total" REAL
);

ALTER TABLE "order"
    ADD CONSTRAINT "fk_order_user_id"
    FOREIGN KEY ("user_id")
    REFERENCES "user"("id");
Oracle 19c+
CREATE TABLE "user" (
    "id" NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    "name" VARCHAR2(255),
    "email" VARCHAR2(255)
);

CREATE TABLE "order" (
    "id" NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    "user_id" NUMBER,
    "total" NUMBER
);

ALTER TABLE "order"
    ADD CONSTRAINT "fk_order_user_id"
    FOREIGN KEY ("user_id") REFERENCES "user"("id");
diagram2code · Back to Converter · GitHub