Schema code

Write an ER diagram as text, one block per table, and bring schemas in from SQL or DBML files.

Schema code is the text form of an ER diagram. The Schema code pane beside the canvas shows it, and you can edit it there: the diagram follows when you pause typing. The assistant and a connected AI app write the same code, so knowing it helps you read what they draft. The layout is automatic, so the code only says what the tables hold and how they relate.

A first schema

Paste this into the Schema code pane to see it drawn.

Schema code
Project shop {
  database_type: 'PostgreSQL'
  Note: 'What the shop sells and who bought what'
}

Enum order_status {
  pending
  paid
  shipped
}

Table customers {
  id integer [pk, increment]
  email varchar(255) [unique, not null]
  name varchar(120) [not null]
}

Table orders {
  id integer [pk, increment]
  customer_id integer [not null, ref: > customers.id]
  status order_status [not null, default: 'pending']
  placed_at timestamp [not null, default: `now()`]
}

Reading it from the top:

  • Project shop { … } names the diagram, says which database it's for, and adds a note.
  • Enum order_status lists the values a status can have.
  • Each Table block lists its columns, one per line: a name, a type, and settings in square brackets.
  • [pk, increment] makes id the primary key, numbered by the database.
  • ref: > customers.id makes orders.customer_id point at customers.id: many orders to one customer.
  • status has the enum's name as its type, and starts as 'pending'.

The rules that matter

  1. Blocks open with { and close with }. Tables, enums, groups, notes and the Project are each a block. The app writes each line of a block on a line of its own, and the closing } too.
  2. One column per line. A column is a name, then a type, then optional settings in square brackets, separated by commas: email varchar(255) [unique, not null].
  3. Every name a relationship uses must exist. A ref to a table or column that isn't in the code is a mistake, and the pane says which.
  4. Text goes in single quotes, names in double quotes. default: 'pending' and Note: 'One row per order' are text. A name with spaces or symbols goes in double quotes: "order items".
  5. Keywords ignore case, names don't. table works like Table, and NOT NULL like not null. But Users and users are two different tables.

The Project block

Project shop {
  database_type: 'PostgreSQL'
  Note: 'What the shop sells and who bought what'
}

The Project block is optional, and there's at most one. Its name is the diagram's title. database_type says which database the schema is for, such as 'PostgreSQL', 'MySQL' or 'SQL Server'; the assistant writes types for it. Note says what the schema is for. In the app these are Title, Database and Note in Properties when nothing is selected.

Tables and columns

Table order_items {
  id integer [pk, increment]
  order_id integer [not null, ref: > orders.id]
  sku varchar(64) [not null]
  quantity integer [not null, default: 1]
  unit_price decimal(10,2) [not null]
  gift_message text [note: 'Printed on the packing slip']
  Note: 'One row per product in an order'
}

A column's type is written as you would for the database: integer, bigint, varchar(255), text, boolean, timestamp, date, decimal(10,2), uuid, jsonb, or the name of an enum. The app keeps the type as written, and only checks that a foreign key's type matches the key it points at.

Settings go in square brackets after the type, separated by commas:

SettingWhat it means
pkPart of the primary key. A key column always needs a value.
incrementThe database numbers new rows on its own.
uniqueNo two rows can have the same value.
not nullThe column always needs a value. Without it, a column can be empty.
nullThe column can be empty, which is already the default.
default: <value>The value a new row gets when none is given.
note: '<text>'What the column holds, when its name doesn't say.
ref: > <table>.<column>The column points at another table's key. See Relationships.

A default is a number, true or false, null, text in single quotes, or an expression in backticks: default: 0, default: true, default: 'draft', default: `now()`.

Note: on a line of its own inside the table says what one row is.

A table needs at least one column, and each column name can be used once in a table.

Keys and indexes

A single-column key is the pk setting. For anything else, add an indexes block inside the table, one index per line:

Table memberships {
  user_id integer [not null, ref: > users.id]
  team_id integer [not null, ref: > teams.id]
  role varchar(20) [not null]
  joined_at timestamp [not null]
  indexes {
    (user_id, team_id) [pk]
    joined_at
    role [name: 'ix_memberships_role', type: btree]
  }
}
  • (a, b) [pk] makes the columns together the primary key. Use it for a junction table.
  • email [unique] is a unique index, and (a, b) [unique] makes the pair unique.
  • A line with just a column, or columns in brackets, is a plain index, for columns that are searched or joined often.
  • name: sets the index's name in SQL, type: its kind, such as btree or hash, and note: a note.
  • An index can be on an expression in backticks: `lower(email)` [unique].

Relationships

There are two ways to write a relationship. On the column that holds the reference, with ref: in its settings:

Table orders {
  id integer [pk, increment]
  customer_id integer [not null, ref: > customers.id]
}

Or on a line of its own, outside the tables, starting with Ref::

Ref: orders.customer_id > customers.id

Both say the same thing. The operator between the two columns says how many rows are on each side:

OperatorKindExample
>Many to oneorders.customer_id > customers.id: many orders, one customer.
<One to manycustomers.id < orders.customer_id: the same, written from the other side.
-One to oneprofiles.user_id - users.id: one profile per user. Make profiles.user_id unique.
<>Many to manyposts.id <> tags.id. Better as a junction table with a foreign key to each side; the checks warn about it.

A Ref: line can do more than a ref: setting:

Ref fk_orders_customer: orders.customer_id > customers.id [delete: cascade, update: no action]
Ref: order_lines.(order_id, line_no) > order_rows.(order_id, line_no)
  • A name after Ref is the foreign key's name in SQL.
  • delete: and update: say what the database does to these rows when the row they point at is deleted, or its key changes: cascade, restrict, set null, set default or no action.
  • Columns in brackets, table.(a, b), relate a composite key. Both sides need the same number of columns.

Several relationships can share one block: Ref { … }, with one relationship per line inside.

A column can point at its own table, such as a comment's parent_id at comments.id. Two columns can only be related once.

Enums

Enum order_status {
  pending
  paid [note: 'Money received']
  shipped
  "on hold"
}

An enum lists its values, one per line. A value can have a note, and a value with spaces goes in double quotes. A column uses the enum by giving its name as its type: status order_status. An enum needs at least one value.

Groups

TableGroup billing {
  invoices
  invoice_lines
  payments
  Note: 'Everything to do with getting paid'
}

A group lists the tables it frames, one per line, and can have a note. A table can be in one group.

Notes

Notes say what names can't. A table, a column, an enum value, a group and the Project can each have one, as shown above. For a note of several lines, use three single quotes:

Table orders {
  id integer [pk, increment]
  Note: '''
    One row per checkout.
    Rows are never deleted, only cancelled.
  '''
}

A note of its own, outside any table, is a Note block with a name: Note overview { 'Read me first' }. The app keeps it in the code, but the canvas doesn't draw it.

Schemas, aliases and colours

Table billing.invoices as inv [headercolor: #3498DB] {
  id integer [pk, increment]
}
  • billing.invoices puts the table in the billing schema. Without a schema, a table is in the default one. The SQL for PostgreSQL and SQL Server creates the schema for you.
  • as inv gives the table a short alias that relationships can use: ref: > inv.id.
  • headercolor on a table, and color on a group or a Ref: line, are kept in the code. The canvas draws every table the same way.

Quotes

Put a table, column or enum value name in double quotes when it has spaces or symbols: "order items", "line no". Inside double quotes, write \" for a quote and \\ for a backslash. Text in single quotes works the same way, with \' for a quote.

Plain names, made of letters, digits and underscores, don't need quotes. The assistant writes names in lower case with underscores, such as order_items, so they never need them.

Comments

// Billing tables come later
Table customers {
  id integer [pk, increment]
}

A line that starts with // is a comment. The app ignores it, and keeps it in the text when the diagram changes elsewhere, on the canvas or by the assistant.

As you type

The pane suggests what can come next: the block keywords at the start of a line, a type after a column's name (your enums included), the settings inside square brackets, table.column after a relationship's operator, and table names inside a group.

Ids

Every table, column, relationship, enum and group has an id, such as Table_Orders, Column_OrdersCustomerId or Ref_OrdersCustomerId. Comments, the assistant and connected AI apps refer to elements by id. When you edit the code, the app keeps each element's id while it's named the same: a table or an enum by its name, a column by its name in the same table, and a relationship while it joins the same columns. So you can reorder blocks and add new ones, and comments stay on their elements.

Renaming a table or a column in the code gives it a new id. To rename something and keep its comments, rename it on the canvas or in Properties.

When a line has a mistake

If a line can't be read, the pane's header shows the message with its line number, and so does the Problems panel. The diagram keeps its last working version until you fix it.

MessageWhat to do
Close the block with }.Add the closing } of the table, enum, group or Project.
Close the settings with ].Add the closing ] after a column's settings.
Separate the settings with commas, and close them with ].Put a comma between settings, such as [pk, increment].
There's no table … Add it, or fix the name.A relationship names a table that doesn't exist. Check the spelling and the case.
… has no column …A relationship names a column that isn't in that table.
There's already a table … Give each table its own name.Rename one of the two tables.
This table already has a column …Rename or remove one of the two columns.
This enum already has the value …Remove the repeated value.
Give the column a type, e.g. id integer.Add a type after the column's name.
Add a column to the table, e.g. id integer [pk].A table needs at least one column.
These columns are already related.Remove the second relationship between the same columns.
A relationship needs two different columns.Point the relationship at another column.
Give the enum at least one value.Add a value to the enum.
… is already in group … A table can be in one group.Take the table out of one of the groups.
Unknown setting …A column takes pk, unique, not null, null, increment, default, note and ref. Check the spelling.
Start the line with Table, Ref, Enum, TableGroup, Note or Project.Every block starts with one of these words.

Import a file

You can bring in a schema from a SQL script or a DBML file. In the Diagrams panel, open New diagram (the plus button) and choose Import SQL or DBML file…. The app reads .sql, .dbml and .txt files.

  • A .sql file, or another file with CREATE TABLE in it, is read as SQL. The app tries PostgreSQL, then MySQL, then SQL Server, and uses the first that reads it. Its tables, columns, keys, indexes, foreign keys and enums come in.
  • A .dbml file is read as schema code, which is DBML.

The file becomes a new diagram, named after its Project or the file. A file with no tables in it isn't imported, and the message says so. If the SQL can't be read as any of the three databases, the message says why, with the line number when there is one.

Export a file

Export at the top right of the diagram downloads the schema as schema code (a .dbml file), as SQL for PostgreSQL, MySQL or SQL Server, as a Mermaid file, or as a picture. See Export for what each one holds.

Full reference

The schema code reference has the complete syntax on one page, exactly as the assistant gets it, with the reference schemas it draws from.

On this page