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.
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_statuslists the values a status can have.- Each
Tableblock lists its columns, one per line: a name, a type, and settings in square brackets. [pk, increment]makesidthe primary key, numbered by the database.ref: > customers.idmakesorders.customer_idpoint atcustomers.id: many orders to one customer.statushas the enum's name as its type, and starts as'pending'.
The rules that matter
- 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. - 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]. - Every name a relationship uses must exist. A
refto a table or column that isn't in the code is a mistake, and the pane says which. - Text goes in single quotes, names in double quotes.
default: 'pending'andNote: 'One row per order'are text. A name with spaces or symbols goes in double quotes:"order items". - Keywords ignore case, names don't.
tableworks likeTable, andNOT NULLlikenot null. ButUsersandusersare 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:
| Setting | What it means |
|---|---|
pk | Part of the primary key. A key column always needs a value. |
increment | The database numbers new rows on its own. |
unique | No two rows can have the same value. |
not null | The column always needs a value. Without it, a column can be empty. |
null | The 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 asbtreeorhash, andnote: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.idBoth say the same thing. The operator between the two columns says how many rows are on each side:
| Operator | Kind | Example |
|---|---|---|
> | Many to one | orders.customer_id > customers.id: many orders, one customer. |
< | One to many | customers.id < orders.customer_id: the same, written from the other side. |
- | One to one | profiles.user_id - users.id: one profile per user. Make profiles.user_id unique. |
<> | Many to many | posts.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
Refis the foreign key's name in SQL. delete:andupdate: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 defaultorno 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.invoicesputs the table in thebillingschema. Without a schema, a table is in the default one. The SQL for PostgreSQL and SQL Server creates the schema for you.as invgives the table a short alias that relationships can use:ref: > inv.id.headercoloron a table, andcoloron a group or aRef: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.
| Message | What 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
.sqlfile, or another file withCREATE TABLEin 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
.dbmlfile 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.