dbml-diff
v0.7.0
Published
Structural diff for DBML schema files - emits text, JSON, or an annotated DBML document that renders as a visual diff in dbdiagram.io
Maintainers
Readme
dbml-diff
Structurally diff two DBML schema files and emit the result as text, JSON, or an annotated DBML document that renders as a visual diff in dbdiagram.io.
No install needed:
npx dbml-diff old.dbml new.dbml # readable text summary
npx dbml-diff old.dbml new.dbml --format dbml -o diff.dbml # visual diff, paste into dbdiagram.ioThe visual diff shows only what changed - added, removed, and modified tables, with per-column annotations:
Why
If you keep your database schema as DBML in version control, git diff between two versions is line-noise: attribute reordering, whitespace, and hundreds of unchanged lines drown the handful of real changes. dbdiagram.io has no built-in version compare, and existing schema-diff tools target live databases, not DBML files. dbml-diff compares the two documents structurally - tables, columns, types, nullability, primary keys, enums - and tells you exactly what changed. Related upstream issue: holistics/dbml#175.
Install
npm i -g dbml-diff # or keep using npxCLI usage
dbml-diff <old.dbml> <new.dbml> [options]
Options:
--format <text|json|dbml> output format (default: text)
--full-new-tables in dbml format, emit full column lists for
added tables (default: stub to PK + note with
column count, because full definitions drown
the diagram at scale)
--colors in dbml format, use headercolor annotations
(#2ecc71 added / #f39c12 modified / #e74c3c
removed) instead of relying on name prefixes
alone. Requires dbdiagram paid tier to render;
name prefixes are always emitted regardless.
--hide-unchanged-pk in dbml format, drop the unchanged primary-key
row from modified tables (leaner delta-only view)
--migrate emit a T-SQL migration script (ALTER/CREATE DDL)
instead of a diff; DROP and heuristic RENAME
statements are commented out. Cannot be combined
with --format. Honors -o.
--include-notes treat a changed column note as a column change
(reported as "note changed"); off by default
-o, --output <file> write to file instead of stdout
-h, --help usage
--version package versionThe counts summary (added: N, removed: N, modified: N) always goes to stderr, so stdout stays pipeable.
Visual diff conventions (--format dbml)
| Marker | Meaning |
| --- | --- |
| NEW · table name prefix | Table added |
| MOD · table name prefix | Table modified |
| DEL · table name prefix | Table removed |
| __ADDED column suffix | Column added to a modified table |
| __REMOVED column suffix | Column removed from a modified table |
| __RENAMED column suffix | Rename candidate (heuristic - verify; never merged silently) |
| __CHANGED column suffix | Type, nullability or PK membership changed (detail in the column note) |
| NEW · / MOD · / DEL · enum name prefix | Enum added / modified / removed |
| [note: 'ADDED'] / [note: 'REMOVED'] on an enum value | Value added / removed in a modified enum |
Modified tables show only their primary key (annotated unchanged columns omitted) plus the changed columns; --hide-unchanged-pk drops that PK row for a leaner delta-only view (the block stays valid because a modified table always has at least one changed column). Added tables are stubbed to the PK with a NEW TABLE - N columns note by default (--full-new-tables emits everything); removed tables are emitted in full. Enum changes are emitted as Enum blocks under the same NEW · / MOD · / DEL · prefixes; in a modified enum the full new value list is shown with ADDED notes on new values and the dropped values re-listed with REMOVED notes. A DIFF SUMMARY table at the top lists the counts: one column per metric, with the label as the column name and the count as the column type, so the numbers are visible on the canvas at a glance. It's a real table (not a standalone Note block) because dbdiagram renders standalone notes as Sticky Notes only on paid tiers, whereas a table always renders on the free tier. The three table counts (added / removed / modified) always appear; enum, ref, and TableGroup rows appear only for categories that changed.
Relationship (Ref:) changes are counted in the DIFF SUMMARY table (added, removed, retargeted when an FK side keeps its columns but points at a new parent, and unresolved for a change that cannot be mapped to a single retarget). The per-ref and per-group detail - which tables changed and how - lives in --format text and --format json, not in the diagram.
Viewing the diff in dbdiagram.io
dbml-diff old.dbml new.dbml --format dbml -o diff.dbml- Open dbdiagram.io and create a new diagram.
- Paste the contents of
diff.dbmlinto the editor. - The diagram now shows only what changed: scan for the
NEW ·/MOD ·/DEL ·tables, and hover the annotated columns to read the change notes. With--colors(paid tier) the table headers are colour-coded too.
Migration script (--migrate)
--migrate emits a T-SQL migration script (SQL Server / Azure Synapse) that
transforms a database from the old schema to the new one, instead of a diff:
dbml-diff old.dbml new.dbml --migrate -o up.sqlWhat it emits live (uncommented):
- Added tables become
CREATE TABLE(with aPK_<table>constraint when the table has a primary key). - Added columns become
ALTER TABLE ... ADD. - Type or nullability changes become
ALTER TABLE ... ALTER COLUMN(the full target type is restated, as T-SQL requires). - Added foreign keys become
ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY(with an inline-- NOTEthat it fails if existing rows violate the constraint).
For safety, destructive and heuristic statements are emitted commented out, so a pasted script cannot cause data loss on a straight run - review and uncomment them deliberately:
- Removed tables (
-- DROP TABLE ...) and removed columns (-- ALTER TABLE ... DROP COLUMN ...). - Rename candidates (
-- EXEC sp_rename ...), which are heuristic - verify before running. - Removed foreign keys, and the old side of a retargeted foreign key
(
-- ALTER TABLE ... DROP CONSTRAINT ...). A retargeted key comments the old drop and emits the newADD CONSTRAINTlive. - Ambiguous (unresolved) ref changes, emitted as an
-- UNRESOLVED ref changecomment for you to resolve by hand.
Caveats:
- Adding a
NOT NULLcolumn to a table that already has rows fails without a default, and tightening an existing column toNOT NULLfails if it holds any NULLs; both carry an inline-- NOTE. - Enums and TableGroups are not represented in the SQL output.
- The generated FK constraint name (
FK_<child>_<parent>_<childCols>) is synthesized and unqualified, so it will usually differ from the real constraint name in your database. Adjust the name on anyDROP CONSTRAINTline before uncommenting it. --migratecannot be combined with--format, and T-SQL is currently the only dialect.
Programmatic API
const { diff, emitText, emitJson, emitDbml, emitMigration } = require('dbml-diff');
const result = diff(oldDbmlString, newDbmlString);
// {
// tables: {
// added: [ { name, columns: [...] } ],
// removed: [ { name, columns: [...] } ],
// modified: [ {
// name,
// columnsAdded: [...], columnsRemoved: [...],
// columnsChanged: [ { column, changes: ['type X -> Y', ...] } ],
// renames: [ { from, to } ] // heuristic candidates only
// } ]
// },
// enums: {
// added: [ { name, values: [...] } ],
// removed: [ { name, values: [...] } ],
// modified: [ { name, values: [...], valuesAdded: [...], valuesRemoved: [...] } ]
// },
// refs: {
// added: [ { from: { table, columns }, to: { table, columns } } ],
// removed: [ { from, to } ],
// retargeted: [ { from, oldTo, newTo } ], // same FK side, new parent
// unresolved: [ { from, oldTargets: [...], newTargets: [...] } ] // ambiguous
// },
// groups: { // TableGroups (membership diffed as a set)
// added: [ { name, tables: [...] } ],
// removed: [ { name, tables: [...] } ],
// modified: [ { name, tablesAdded: [...], tablesRemoved: [...] } ]
// },
// counts: { added, removed, modified } // tables only
// }
console.log(emitText(result));
console.log(emitJson(result));
console.log(emitDbml(result, { oldLabel: 'v1', newLabel: 'v2', colors: true }));
console.log(emitMigration(result, { oldLabel: 'v1', newLabel: 'v2' })); // T-SQL migration scriptExit codes
Useful for CI gates ("fail the build if the schema changed"):
| Exit code | Meaning |
| --- | --- |
| 0 | Schemas are identical |
| 1 | Differences found |
| 2 | Error (bad arguments, unreadable file, DBML parse failure) |
Parsing behaviour
- dbdiagram-specific
DiagramViewtop-level blocks are stripped before parsing (@dbml/corerejects them).TableGroupblocks are kept and diffed for group-membership changes. - Primary keys are detected from inline
[pk]attributes and fromIndexes { Col [pk] }blocks. - Schema-qualified table names (e.g.
dbo.Shipments) are preserved as-is.
Roadmap
Visual public roadmap - what shipped, what's in progress, what's next. Generated from the issue tracker: issues labelled roadmap become cards, status: labels set the column, closed issues land in Done.
--migrateT-SQL migration script (CREATE/ALTER, foreign-key constraints, commentedDROP/rename) - the ALTER-generation ask in upstream holistics/dbml#175.
License
Apache-2.0.
