Free · No sign-up · Works with .mmd files

Mermaid ER diagram editor

An entity-relationship diagram shows tables, their columns, and how they join. Use one when the subject is a database schema and the interesting questions are about keys and cardinality — one-to-many, optional, or through a join table. For types with behaviour and inheritance, use a class diagram instead.

A normalised order schema with a join table

The `}o--||` on ORDER_ITEM is the part worth studying: it is how you express a many-to-many relationship honestly, through a join table with its own columns, rather than pretending two tables connect directly.

erDiagram
    CUSTOMER ||--o{ ORDER : places
    ORDER ||--|{ ORDER_ITEM : contains
    PRODUCT ||--o{ ORDER_ITEM : "appears in"
    CUSTOMER ||--o{ ADDRESS : "ships to"

    CUSTOMER {
        int id PK
        string email UK "lowercased on write"
        string name
        datetime created_at
    }
    ORDER {
        int id PK
        int customer_id FK
        string status
        decimal total_amount
    }
    ORDER_ITEM {
        int order_id PK, FK
        int product_id PK, FK
        int quantity
        decimal unit_price "price at time of order"
    }
    PRODUCT {
        int id PK
        string sku UK
        string name
    }
    ADDRESS {
        int id PK
        int customer_id FK
        string postcode
    }
Open this in the editor
Advertisement

Worked examples

1. Two entities and one relationship

Every ER relationship needs a label after the colon — it is not optional, unlike a flowchart edge. Read it as a sentence: CUSTOMER places ORDER.

erDiagram
    CUSTOMER ||--o{ ORDER : places
Open in the editor

2. Adding columns

Each attribute line is `type name`, and both parts are required. `PK`, `FK` and `UK` mark keys; a quoted string at the end is a comment.

erDiagram
    CUSTOMER ||--o{ ORDER : places
    CUSTOMER {
        int id PK
        string email UK
        string name
    }
    ORDER {
        int id PK
        int customer_id FK
        datetime placed_at "UTC"
    }
Open in the editor

3. Cardinality that says something

The two characters nearest each entity are its cardinality. This example says an order must have at least one line, but a customer may have no orders at all — a real constraint that the schema should enforce and the diagram should show.

erDiagram
    CUSTOMER ||--o{ ORDER : places
    ORDER ||--|{ ORDER_LINE : contains
    ORDER_LINE }o--|| PRODUCT : references
Open in the editor

4. Many-to-many through a join table

Mermaid can draw a direct many-to-many, but a schema rarely has one — there is a join table underneath. Drawing the join table is more honest and gives you somewhere to put the columns that belong to the relationship itself.

erDiagram
    STUDENT ||--o{ ENROLMENT : "signs up for"
    COURSE ||--o{ ENROLMENT : "is taken via"
    ENROLMENT {
        int student_id PK, FK
        int course_id PK, FK
        datetime enrolled_at
        string grade "null until marked"
    }
    STUDENT {
        int id PK
        string name
    }
    COURSE {
        int id PK
        string code UK
    }
Open in the editor

5. Self-referencing relationships

A table can relate to itself — a reporting line, a category tree, a threaded comment. The relationship label matters more than usual here, because both ends are the same entity.

erDiagram
    EMPLOYEE ||--o{ EMPLOYEE : manages
    EMPLOYEE {
        int id PK
        int manager_id FK "null for the CEO"
        string name
        string job_title
    }
Open in the editor

ER diagram syntax reference

Cardinality is written as two characters at each end of the relationship, and it reads outward from the middle. The left pair describes the left entity; the right pair describes the right entity.

SyntaxMeaning
erDiagramOpens the diagram. Case-sensitive.
A ||--o{ B : labelRelationship. The label is required.
||Exactly one.
o|Zero or one.
}|One or more.
}oZero or more.
--Identifying relationship — solid line.
..Non-identifying relationship — dashed line.
A { ... }Attribute block for entity A.
int id PKAttribute: type, then name, then optional key marker.
PK / FK / UKPrimary, foreign, unique key.
int id PK, FKMultiple key markers, comma-separated.
string email "comment"Trailing quoted string is a comment on the column.
A ||--o{ B : "two words"Quotes are REQUIRED for any label containing a space. Without them the label is cut at the first space and each remaining word becomes a phantom entity.
Advertisement

Errors that break ER diagrams

ER diagrams have the strictest grammar of the six types here — the things that render fine elsewhere will be rejected outright. All reproduced against Mermaid 11.12.2.

What you see

Parse error, ending in: Expecting 'COLON', 'STYLE_SEPARATOR', got 'NEWLINE'

Why

A relationship with no label. Unlike a flowchart edge, the colon and a label are mandatory — the line simply ends too early without them.

Fix

Add a colon and a verb phrase. Keep it short enough to read as a sentence.

Broken
erDiagram
    CUSTOMER ||--o{ ORDER
Fixed
erDiagram
    CUSTOMER ||--o{ ORDER : places

What you see

Parse error, ending in: got 'UNICODE_TEXT'

Why

An invalid cardinality token. Only `||`, `o|`, `}|` and `}o` (and their mirrors) are valid; anything else fails. `oo` is the usual typo, from expecting the notation to be symmetrical.

Fix

Use one of the four pairs. `||--o{` — exactly one to zero-or-more — covers most foreign keys.

Broken
erDiagram
    CUSTOMER ||--oo{ ORDER : places
Fixed
erDiagram
    CUSTOMER ||--o{ ORDER : places

What you see

Parse error, ending in: Expecting 'ATTRIBUTE_WORD', got 'BLOCK_STOP'

Why

An attribute with a name but no type. ER attributes are `type name`, and the type is not optional — which surprises people coming from schemaless databases.

Fix

Give every attribute a type. Invent one if the real schema has none: `string`, `int`, `json`.

Broken
erDiagram
    CUSTOMER {
        email
    }
Fixed
erDiagram
    CUSTOMER {
        string email
    }

What you see

No diagram type detected matching given configuration

Why

Wrong capitalisation. `erdiagram` and `ERDiagram` both fail; only `erDiagram` works.

Fix

Lowercase e, capital D.

Broken
erdiagram
    A ||--o{ B : has
Fixed
erDiagram
    A ||--o{ B : has

What you see

Parse error, ending in: Expecting 'NON_IDENTIFYING', 'IDENTIFYING', got 'UNICODE_TEXT'

Why

The line between the two cardinality markers is wrong. It must be exactly two characters — `--` for an identifying relationship or `..` for a non-identifying one. A single dash is not a shorter version of `--`, it is a syntax error.

Fix

Use `--` or `..` between the cardinality pairs.

Broken
erDiagram
    CUSTOMER ||-o{ ORDER : places
Fixed
erDiagram
    CUSTOMER ||--o{ ORDER : places

What you see

The cardinality renders but describes the opposite constraint

Why

The two characters nearest each entity belong to that entity, and it is easy to read them the wrong way round. `CUSTOMER ||--o{ ORDER` says one customer has zero or more orders. Flip it to `CUSTOMER }o--|| ORDER` and you have said every customer belongs to exactly one order, which is nonsense — and it renders without complaint.

Fix

Read outward from the middle: the marks touching CUSTOMER describe how many customers, not how many orders.

Broken
erDiagram
    CUSTOMER }o--|| ORDER : places
Fixed
erDiagram
    CUSTOMER ||--o{ ORDER : places

Rendering notes

Measured against Mermaid 11.12.2 as this site runs it.

Layout is driven by relationships, not by entity count

A chain of entities each related to the next renders as a tall column — forty entities produce a viewBox around 116×7500 — but the same forty entities related to one central table produce something much wider and shorter. This is the one diagram type here where the shape of your data changes the shape of the picture more than its size does, so a schema that will not fit is often fixed by changing which relationships you draw rather than how many.

The grammar is the strictest of the six types

Relationship labels are mandatory, attribute types are mandatory, and cardinality tokens are a closed set. Compared with Gantt, where almost everything renders and mistakes are silent, ER diagrams fail loudly and early. That is a feature: if an ER diagram renders, it is far more likely to be the diagram you meant.

Labels are HTML, so PNG export re-renders

Entity attribute tables are drawn inside an SVG `<foreignObject>`, so browsers cannot rasterise them to a canvas directly. PNG export on this site used to silently return an SVG file; it now re-renders with plain SVG text labels first, producing a correct full-size PNG with marginally different typography.

Entity names are case-sensitive and conventionally uppercase

`CUSTOMER` and `customer` are two different entities, and referring to both in the same diagram silently gives you two boxes. The uppercase convention is not enforced by Mermaid but is worth keeping precisely because it makes an accidental lowercase reference obvious.

Theme changes colour, never layout

Default and dark themes produce an identical viewBox for the same source, so attribute tables cannot reflow or clip when the theme changes.

When to use something else

If you need inheritance, interfaces or methods, this is the wrong diagram — ER has no concept of any of them. Use a class diagram, and accept that it models your code rather than your tables.

If the schema is large, an ER diagram of the whole thing is a wall chart nobody reads. Draw the five tables involved in the thing you are explaining and leave the rest out; a diagram is an argument, not an inventory.

And if the real question is how data moves between systems rather than how it is stored, a flowchart with subgraphs per system will answer it and an ER diagram will not.

Other diagram types

Written by Dominik Malsch · Last updated:

Open the editor →