DBML vs SQL DDL: Which Should Define Your Schema?
If you’re comparing DBML and SQL DDL, you’ve probably noticed they answer to different bosses. DDL answers to the database: it’s the executable statement that actually creates your tables. DBML answers to humans: it’s a markup language designed to describe a schema in as few keystrokes as possible.
That framing already contains most of the answer, but the details matter — especially the places where DBML’s simplicity quietly stops covering what your database does. Here’s the comparison we’d want if we were choosing.
The same table in both
A users table in PostgreSQL DDL:
CREATE TABLE users (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
display_name VARCHAR(100),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE orders (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
ordered_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_orders_user_id ON orders (user_id);
The same two tables in DBML:
Table users {
id bigint [pk, increment]
email varchar(255) [not null, unique]
display_name varchar(100)
created_at timestamptz [not null, default: `now()`]
}
Table orders {
id bigint [pk, increment]
user_id bigint [not null]
ordered_at timestamptz [not null, default: `now()`]
indexes {
user_id
}
}
Ref: orders.user_id > users.id [delete: cascade]
The DBML version is shorter, reads top-to-bottom without ceremony, and diffs cleanly in a pull request. The DDL version is the one your database will actually run. Both facts matter.
What each format is for
SQL DDL is the ground truth. It’s not a description of your schema — it is your schema, in the only language the database accepts. Everything the database can do is expressible, because DDL is defined by the database itself: partitioning, check constraints, triggers, generated columns, storage parameters, collations, comments. There is no feature gap by construction.
DBML is a design document that happens to be parseable. It was created (by the dbdiagram team) to describe schemas concisely enough that a human writes them by hand and a tool renders diagrams and docs from them. It’s deliberately smaller than SQL: tables, columns, types, primary/foreign keys, uniqueness, defaults, indexes, enums, notes, table groups. That smallness is the feature — and the limitation.
Where DDL wins
- Completeness. DBML has no representation for triggers, stored procedures, partitioning, row-level security, or most vendor-specific storage options, and its coverage of constraints beyond keys and uniqueness has historically lagged behind what real schemas use. If your schema leans on these, a DBML rendition of it is lossy — and a lossy design doc drifts into a misleading one.
- No conversion step. DDL in a migration file is done. DBML has to be translated into DDL before anything runs, and every translation step is a place where what you designed and what you deployed can diverge.
- Dialect precision.
varcharbehaves differently across MySQL, PostgreSQL, and Oracle; DDL forces you to be explicit in the dialect you’ll actually deploy. DBML’s dialect neutrality is convenient at design time and vague at deploy time.
Where DBML wins
- Readability and speed. A 40-table schema in DBML is a document you can read; the same schema as DDL is a script you have to trace. For design reviews with people who don’t write SQL for a living, this is decisive.
- Reviewable design changes. “We’re adding
orders.cancelled_atand arefundstable” is a five-line DBML diff. The equivalent DDL migration is longer and buries the design decision in syntax. - Tooling. DBML exists so diagrams and docs can be generated from text. If your workflow is text-first, DBML is the format that ecosystem speaks — we compared the leading tool in that ecosystem in our dbdiagram comparison.
The real question: what is your source of truth?
Most “DBML vs DDL” decisions are really this question in disguise. Three honest configurations:
- DDL (migrations) as truth, DBML as generated documentation. The safest default for a running product. Your migration chain is authoritative; DBML/diagrams are regenerated from it and never edited by hand. The risk is stale docs — which is why the generation should be automated, not a quarterly chore.
- DBML as truth, DDL generated from it. Attractive for greenfield design, before anything is deployed. It gets risky the moment production migrations start, because generated DDL and hand-tuned migrations will disagree eventually, and then you have two sources of truth.
- A modeling tool as truth, both formats as import/export. The schema lives in a model that’s richer than either format — and DBML and DDL are both views of it. This is where Sqemo sits, so treat this section as the part where we have a horse in the race.
What neither format answers: where names come from
Both DBML and DDL record that a column is called usr_id. Neither can tell you it should have been user_id under your team’s convention — both formats faithfully preserve whatever the person typing believed the standard was. If you’ve watched a schema accumulate userId, user_id, and usr_id in three different tables, you’ve seen the failure mode; our naming conventions guide covers why that’s expensive to unwind.
This is the layer Sqemo adds above both formats. You maintain a logical model (“Member”, “Order Date”) plus a glossary and naming rules, and physical names (MBR, ORD_DT) are generated rather than typed. DBML and SQL DDL become interchange formats: import either, export either (SQL in seven dialects — MySQL, PostgreSQL, Oracle, SQL Server, SQLite, CUBRID, H2), while the source of truth carries something neither format can — a machine-checkable definition of what every name should be. That’s what lets npx sqemo-mcp lint fail a pull request when a name drifts from the standard.
If your team has no naming standard and no plans for one, that layer is machinery you don’t need, and the simpler DDL-as-truth setup is the right call.
So which one?
Write DDL directly if:
- You have a running product with a migration chain — that chain is already your source of truth, and adding a second authoritative format creates drift, not clarity.
- Your schema uses features DBML can’t express (triggers, partitioning, RLS, vendor-specific options).
- Diagrams are an occasional need, not a workflow — generate them from the live schema when asked.
Use DBML if:
- You’re designing something new and want fast, reviewable, text-first iteration before committing to a dialect.
- Your team reviews schema design as documents, and diagram/doc generation from text is your workflow.
Use a model above both if:
- You need logical/physical separation, or a naming standard that’s enforced by tooling instead of by review discipline.
- You want DBML and DDL as exits rather than as the thing itself.
Trying the third option costs nothing: open the app — no signup, your project is a local file — and import the schema you already have, as either SQL DDL or DBML. Both doors work in both directions.