SQL to ER diagram
SQL to ER diagram: paste CREATE TABLE, get a crow's foot diagram
In short
Paste the CREATE TABLE statements from Postgres, MySQL, SQLite or SQL Server and this page draws the entity-relationship diagram: one entity per table, columns with their types, and a crow's-foot relationship for every foreign key. Cardinality is inferred from the constraints — a NOT NULL foreign key is a mandatory one-to-many, a UNIQUE one is one-to-one, and a composite primary key made of two foreign keys marks a many-to-many junction. You also get the schema as Mermaid erDiagram code.
Diagram
Entities
| Table | Columns | Primary key | Foreign keys |
|---|
Relationships
| Parent (referenced) | Child (holds FK) | FK column | Constraints | Inferred cardinality |
|---|
Mermaid erDiagram code
Runs in your browser — the SQL is parsed locally and never uploaded. Only the finished diagram spec (table and column names, no data) is sent to our free image endpoint to draw the picture; nothing is stored.
Updated · by the ChartCraft team
How does the SQL get turned into an ER diagram?
A small SQL parser (not a regular expression) tokenises your script, reads every CREATE TABLE and ALTER TABLE … ADD FOREIGN KEY, and builds a model of tables, columns, keys and references. Each table becomes an entity box; each foreign key becomes a relationship line from the referenced (parent) table to the table that holds the key (child). The crow's-foot marks come from the constraints on the foreign-key column, using the rules in the table below.
- Tokenise — identifiers (bare,
"quoted",`backticked`,[bracketed]), strings, numbers, punctuation;--,#and/* */comments are skipped. - Parse statements — column definitions with types, inline constraints, table-level constraints; everything else (
CREATE INDEX,INSERT,SET, dialect options such asENGINE=InnoDB) is skipped and listed in the notes. - Resolve — table-level primary keys and unique constraints are applied to their columns; foreign keys are matched to the referenced tables by name (case-insensitive).
- Infer cardinality — from
NOT NULL,UNIQUEand composite primary keys (rules below). - Emit — a diagram spec for the free image API, the two tables above, and Mermaid
erDiagramcode with the matching crow's-foot arrows.
Which SQL syntax does the converter understand?
The common DDL subset shared by Postgres, MySQL/MariaDB, SQLite and SQL Server. Anything outside it is skipped per statement — the rest of the script still converts — and a parse error reports the line and what was expected.
| Construct | Example | How it is read |
|---|---|---|
CREATE TABLE [IF NOT EXISTS] [schema.]name (…) | CREATE TABLE IF NOT EXISTS shop.orders (…) | One entity named orders; the schema prefix is dropped from the label. |
| Column with type | price DECIMAL(10,2) NOT NULL | Column + type as written, including lengths and precision; TIMESTAMP WITH TIME ZONE, INT UNSIGNED, TEXT[] are kept whole. |
| Inline primary key | id SERIAL PRIMARY KEY | Marks the column PK and NOT NULL. Postgres SERIAL/BIGSERIAL, MySQL AUTO_INCREMENT, SQL Server IDENTITY(1,1) and GENERATED … AS IDENTITY are recognised. |
| Table-level (composite) primary key | PRIMARY KEY (order_id, product_id) | Both columns become PK; two FKs covering the whole PK flag the table as a junction (many-to-many). |
| Inline reference | user_id INT NOT NULL REFERENCES users(id) | Relationship users → orders on user_id. ON DELETE/ON UPDATE, MATCH, DEFERRABLE are accepted and ignored. |
| Table-level foreign key | [CONSTRAINT fk] FOREIGN KEY (a, b) REFERENCES t (x, y) | Composite foreign keys are supported; the label lists both columns. |
ALTER TABLE … ADD [CONSTRAINT …] FOREIGN KEY | ALTER TABLE orders ADD FOREIGN KEY (user_id) REFERENCES users(id); | Merged into the table it targets; also ADD PRIMARY KEY and ADD UNIQUE. |
UNIQUE (inline or table-level) | email VARCHAR(255) UNIQUE, UNIQUE (a, b) | Single-column uniqueness marks the column UK; multi-column uniqueness is only used for cardinality inference. |
NOT NULL / NULL | manager_id INT NULL | Decides whether the parent side of a relationship is mandatory (||) or optional (|o). |
| Quoted identifiers | "Order Items", `orders`, [dbo].[Users] | All three quoting styles; names with spaces are quoted in the Mermaid output. |
| Comments | -- line, # line (MySQL), /* block */ | Skipped anywhere, including inside column lists. |
| Dialect tails and options | ENGINE=InnoDB DEFAULT CHARSET=utf8mb4, WITHOUT ROWID, COMMENT 'x', COLLATE | Ignored. |
DEFAULT expr, CHECK (…), generated columns | status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending','paid')) | Skipped with balanced-parenthesis matching, so a comma inside a CHECK does not break the column list. |
| Secondary indexes | KEY idx_name (name), CREATE INDEX … | Not part of the ER model — skipped and noted. |
| Not supported | CREATE TABLE … AS SELECT, CREATE TYPE, views, triggers, stored procedures | Skipped with a note; the error list tells you the line. |
How is the cardinality inferred from the constraints?
A foreign key always points from the child row to exactly one parent row at most, so the child side is never "many parents". What the constraints decide is whether that parent is mandatory and whether the parent may have more than one child. The table reads parent → child, the same direction as the Mermaid arrows it produces.
| FK column is … | Parent side | Child side | Inferred relationship | Mermaid |
|---|---|---|---|---|
NOT NULL, not unique | exactly one (||) | zero or more (o{) | one-to-many, parent mandatory — the common case | users ||--o{ orders |
| nullable, not unique | zero or one (|o) | zero or more (o{) | optional one-to-many — the child may exist without a parent (e.g. manager_id on employees) | employees |o--o{ employees |
NOT NULL + UNIQUE (or the FK is the whole PK) | exactly one (||) | exactly one (||) | one-to-one — each parent has at most one child, each child exactly one parent | users ||--|| profiles |
nullable + UNIQUE | zero or one (|o) | exactly one (||) | optional one-to-one | users |o--|| profiles |
| composite PK made of exactly two FKs | — | — | the table is a junction: a many-to-many between the two referenced tables, drawn as two one-to-many lines plus a note | orders }o--o{ products |
| no FK at all | — | — | no relationship; the entity is drawn standalone | — |
Strictly, the child-side mark can never be proven from DDL alone: a database does not record that every customer must have at least one order, so the tool never claims a mandatory many (|{). Promote it by hand in the Mermaid code or the studio when your business rule says so. The full notation is on the crow's foot notation page.
What does the Mermaid erDiagram output look like?
Each table becomes an entity block listing type name KEY per column (PK, FK, UK, or combinations such as PK, FK), followed by one relationship line per foreign key, labelled with the FK column. The four-table sample produces this; it pastes straight into GitHub, Notion, Obsidian or the Mermaid live editor.
erDiagram
users {
SERIAL id PK
VARCHAR(255) email UK
VARCHAR(120) full_name
TIMESTAMP created_at
}
orders {
SERIAL id PK
INTEGER user_id FK
VARCHAR(20) status
TIMESTAMP placed_at
}
order_items {
INTEGER order_id PK, FK
INTEGER product_id PK, FK
INTEGER quantity
INTEGER unit_cents
}
products {
SERIAL id PK
VARCHAR(40) sku UK
VARCHAR(200) name
INTEGER price_cents
}
users ||--o{ orders : "user_id"
orders ||--o{ order_items : "order_id"
products ||--o{ order_items : "product_id"
%% order_items is a junction table: orders }o--o{ products
Mermaid attribute types cannot contain spaces or commas, so DECIMAL(10,2) is written DECIMAL(10_2) and TIMESTAMP WITH TIME ZONE becomes TIMESTAMP_WITH_TIME_ZONE; everything else is verbatim. Symbol meanings are on the ER diagram symbols page.
Questions
Which SQL dialects does the SQL to ER diagram converter support?
PostgreSQL, MySQL and MariaDB, SQLite and SQL Server (T-SQL) DDL all parse, because the converter reads the shared CREATE TABLE grammar and skips dialect-specific tails such as ENGINE=, WITHOUT ROWID or IDENTITY(1,1). Oracle DDL mostly works too as long as it uses standard constraints.
Does my schema leave my browser?
The SQL text never leaves the page: tokenising, parsing and cardinality inference run in JavaScript on your machine. To draw the image, the derived diagram spec (table names, column names and types, relationship types) is sent to ChartCraft's free image endpoint, which renders it and returns an SVG without storing anything. The Mermaid code and the two tables are produced entirely locally.
Why is a relationship drawn one-to-many when I expected one-to-one?
Because the foreign-key column has no UNIQUE constraint. From DDL alone, a non-unique FK can repeat, so several child rows may point to one parent. Add UNIQUE to the FK column (or make it the primary key) and the converter switches to one-to-one, following the rules in the cardinality table.
How are many-to-many relationships detected?
A table whose primary key is composite and consists of exactly two foreign keys — the classic junction such as order_items (order_id, product_id) — is tagged as a junction. The diagram keeps the two one-to-many lines (that is what the database actually has) and adds a Mermaid comment with the equivalent }o--o{ many-to-many line.
What happens if there is a syntax error in my SQL?
The statement that fails is skipped and reported with its line number, column and what the parser expected (for example Line 9, column 48: Expected a data type for column name, found ")"). All the other statements still convert, so one typo does not blank the diagram.
Can it read foreign keys added with ALTER TABLE?
Yes. ALTER TABLE child ADD [CONSTRAINT name] FOREIGN KEY (col) REFERENCES parent (col) is merged into the child table, as are ADD PRIMARY KEY and ADD UNIQUE. This matters for pg_dump output, which emits every foreign key as an ALTER statement after the tables.
Where can I paste the Mermaid code?
Anywhere Mermaid renders: GitHub and GitLab Markdown, Notion, Obsidian, Confluence (with the Mermaid macro), MkDocs, Docusaurus, VS Code's Markdown preview, and the Mermaid live editor. The crow's-foot symbols (||--o{ and friends) are standard Mermaid erDiagram syntax.
Can I edit the diagram after converting?
Click Open in Studio: the SQL is handed to the ChartCraft studio as the prompt for an ER diagram, where you can rename entities, add tables that are not in the DDL yet, change cardinalities and export SVG or PNG. The first three diagrams are free, no account needed.