ERD
Every table, and how they hang together
Tables, columns, keys and the relationships between them, read from the schema that actually ships: a Prisma file, a GraphQL SDL, or the entities and migrations themselves. What you keep is a JSON spec in the repo. Re-run the import and the diagram matches the database again.
Two real ones
Both come from the Relay showcase, a fictional parcel network. Relay's thirteen tables are split into Booking, Network and Movement. Billing was imported straight from a schema.prisma.
Click a table for its columns and relationships. Every table has its own URL, #node=<id>, so you can link someone to exactly one.
What it draws
Every field in the spec, and where it shows up. Only id and name are required on a table; everything else is drawn when it is there.
TABLES
- kind
- What the box is.tableviewenumembeddedAn enum draws its
valuesin place of columns. - columns[]
- Name and type on every row, with a
PK,FKorUQbadge. A nullable column readsname?.default,noteand thefktarget show when you hover the row.indexedis kept in the spec but not drawn. - fkon a column
- The target, written
entity.column. Validate refuses one that points at a table the spec does not have. - schema · description
- The database schema (
public,billing) and one sentence on what the table holds. - sources[]
- File and line. With
meta.repositoryset, the table links to that exact line on GitHub or GitLab.
RELATIONSHIPS
- from · to
- The child (the side holding the foreign key) and the parent it references.
- from_cardinality
to_cardinality - Crow's-foot marks at each end, so an optional parent reads differently from a required one.onezero-or-onemanyone-or-many
- identifying
- Lines are solid. Set
identifying: falseand the line is dashed, for a foreign key that is not part of the child's key. - on_delete
- What deleting the parent does to the child rows. It shows when you hover the line, beside the cardinality.cascaderestrictset-nullno-action
- label · from_column · to_column
- A verb phrase for the line (“booked by”) and the exact columns that join. Validate warns if a named column is not on that table.
LAYOUT
- groups[]
- Bounded contexts, schemas or modules, drawn as a labelled frame behind their tables. A table can be in one group.
- layout.cols · gapX · gapY
- Tables go on a grid,
ceil(√n)columns wide unless you say otherwise. Lines route at right angles, and lines that share a gap each get their own lane. - row · colon a table
- Pin a table to a cell when the automatic placement puts it somewhere unhelpful. No two tables can share a cell.
- cards[]
- Up to four short notes under the diagram, each with a tone: neutral, info, success, warning or danger.
Where it reads from
Two importers turn a schema into a spec in one pass. Without a schema file, the skill reads the code and writes the spec itself.
Models become tables and enums keep their values. @id, @@id, @unique, @@unique and @@map carry over, and an optional field becomes a nullable column. Each @relation becomes a relationship with its join columns and onDelete. The child side is many unless its foreign key column is unique. Every table points back to the line it came from.
With --erd, object types become tables and enums keep their values. A field whose type is another object type becomes a relationship. Cardinality comes from the type itself: a list is many, a ! is required. Without the flag, the same file becomes an endpoint catalogue instead.
The skill reads @Entity classes. @PrimaryGeneratedColumn becomes the key, @Column({ nullable, unique, type }) sets the badges, and @ManyToOne with @JoinColumn becomes a foreign key on that side. @OneToOne and @ManyToMany with @JoinTable map the way you would expect.
Check this one before you trust it. A spec written from source is the agent's reading of your code, not a mechanical translation. It is a first draft, and validate only proves it is consistent, not that it is right.
The skill reads CREATE TABLE, PRIMARY KEY, UNIQUE and REFERENCES across the migration folder, so the result is the schema as the migrations leave it, not as the first one created it.
Same caveat as TypeORM. This is read, not imported. Review the draft once.
The spec you keep
This is the file that goes in the repo and gets reviewed. The HTML is generated from it, and you can throw the HTML away.
{
"schema_version": 1,
"diagram_type": "erd",
"meta": { "title": "Orders" },
"entities": [
{ "id": "customer", "name": "customers",
"columns": [
{ "name": "id", "type": "uuid", "pk": true },
{ "name": "email", "type": "text", "unique": true }
] },
{ "id": "order", "name": "orders",
"columns": [
{ "name": "id", "type": "uuid", "pk": true },
{ "name": "customer_id", "type": "uuid",
"fk": "customer.id", "indexed": true }
] }
],
"relationships": [
{ "from": "order", "to": "customer",
"label": "placed by", "on_delete": "restrict" }
]
}Cardinality defaults to many-to-one, which is what a foreign key usually means, so the relationship above needs no more than its two ends.
IDs are the handles everything else uses. An endpoint lists the table IDs it touches, a docs claim names orders.erd#order as its subject, and a link ending #node=order opens the diagram with that table selected.
The full contract is schemas/erd.schema.json.
What validate stops
Errors are things that would draw a wrong diagram, and they fail the command. Warnings are things a reader would trip on, and they never block a render.
Errors
Exit 1. Each one names the field.
dangling-fka column'sfkpoints at a table that is not in the specdangling-refa relationship or group names an unknown table, and the message lists the ones that existduplicate-columntwo columns with the same name on one tablemulti-groupa table placed in two groupsduplicate-idtwo tables or relationships sharing an ID
Warnings
Printed, never blocking.
emptya table with no columnsenum-valuesan enum with no values listedunknown-columna relationship'sfrom_columnorto_columnis not on that tablesizemore than 25 tables in one diagram. Split it by group
The sentences it writes
A docs section can ask for the erd.entities and erd.relationships generators. Each turns the spec into plain English, so the prose about the schema cannot disagree with the diagram next to it. These two came out of the Relay build word for word:
The shipments table has 7 columns and is keyed on id. reference may be null.erd.entities · relay.erd#shipment
Each shipments row relates to exactly one merchants row. Each merchants row relates to any number of shipments rows. The link is shipments.merchant_id → merchants.id. A merchants row cannot be deleted while shipments rows reference it. The relationship is labelled “booked by”.erd.relationships · relay.erd
What it connects to
From a table to the routes that touch it
List table IDs in an endpoint's entities and the dashboard shows, for every table, which endpoints read or write it.
Next to everything else
Drop the spec in a folder with the others and it becomes one entry in a single file, with the entity-to-endpoint links worked out.
Dashboard →Which commit moved it
With --specs, release notes say which commits touched this diagram.