A local, bun-native CLI that turns your dbt projects into self-contained, interactive ER (and lineage) diagrams. It detects your GitHub account, browses the repos that actually contain dbt (your own, your orgs', and ones shared with you), lets you pick one, and writes a single offline .html — the same interactive canvas as sqltoerdiagram.com, but built from dbt model metadata instead of a pasted SQL file.
Built on royalbhati/sqltoerdiagram by Royal Bhati (MIT) — the SQL→ER canvas app, parser, layout, and renderer are his. The
dbt-erdCLI (cli/,src/dbt/) and the read-only single-file viewer are the additions in this fork. Cheers, Royal 🙏
Local & read-only. It reads your dbt artifacts/files and writes one HTML file to disk — it never modifies the source project or pushes to GitHub. (GitHub modes clone shallowly into a temp dir, then delete it.)
The underlying tool is a single static page that turns pasted CREATE TABLE DDL into a clean, interactive diagram — smooth at hundreds of tables, with two-way SQL editing and link sharing, nothing leaving your browser. dbt-erd reuses its canvas renderer and layout.
- Standard
CREATE TABLE/ALTER TABLEDDL across PostgreSQL, MySQL, SQLite, SQL Server & Snowflake.
- Canvas renderer with cached bitmaps + viewport culling — smooth at hundreds of tables (benchmarked ~120fps while zooming 300 tables / 593 FKs).
- Declutter dense schemas: FK lines are soft by default; hover a table to highlight just its relationships, click to pin focus (fades every unrelated table and line), click empty space to clear.
- Drag tables, scroll / pinch to zoom, and pan.
- Hub-aware layered auto-arrange: the most-connected table is placed on one side with its related tables aligned beside it. Horizontal / Vertical direction and Compact / Comfortable / Spacious spacing live under the Arrange ▾ menu.
- Overlap-free: auto-arrange runs a separation pass so no two tables overlap.
- Your arrangement is saved: positions and the camera persist automatically, so reloading restores your exact layout. Editing SQL keeps your manual positions — only brand-new tables get auto-placed beside the rest. Arrange re-runs layout on demand.
- Double-click a table name, column name, or column type to edit it inline. The
change is applied as a surgical text edit (comments, formatting, and unsupported
clauses are preserved), and a table rename updates every
REFERENCESto it. - Add columns: pin a table, then + add column. The new column is inserted into your SQL with a default type for the selected dialect (PostgreSQL / MySQL / SQLite / SQL Server / Snowflake) and opens inline so you can name it. Editing a column type shows dialect-aware suggestions.
- A bottom-left palette adds sticky notes and group boxes to label and cluster sections. Drag to move, drag the corner to resize, double-click to edit text, click to select (colour swatches + delete), or press Delete. They're part of the diagram — included in saves, share links, and PNG/SVG exports.
- Save / Open projects: Save downloads a
.jsonproject (SQL + layout + camera- dialect); Open loads one back.
- Share link: Share copies a URL with the entire project encoded in the hash —
gzip-compressed + base64. The
#…fragment is never sent to a server, so sharing needs no backend, and opening the link restores the exact diagram. - Export to PNG (raster) and SVG (vector).
- Syntax-highlighted SQL editor: keywords / types / strings / comments / numbers are colored via a paint layer behind the textarea. Re-tokenizing is a single linear pass coalesced to one animation frame, so typing stays instant (~6ms full repaint on a 45KB / 300-table script, sub-ms on normal schemas).
- Hide the SQL panel (⬚ in the toolbar) for a full-width diagram.
- Light + dark themes, and it remembers your last schema locally.
bun install
bun run dev # http://localhost:5173bun run build # outputs static files to dist/
bun run preview # preview the production build locallydist/ is plain static HTML/JS/CSS — drop it on any static host:
- GitHub Pages — push
dist/to agh-pagesbranch, or use an action. - Netlify / Vercel / Cloudflare Pages — build command
bun run build, publish dirdist. - Any web server / S3 bucket — just upload the contents of
dist/.
dbt-erd is a local, dbt-specialized CLI that turns a dbt project into the same
self-contained, interactive canvas ERD — no SQL file, no hosting. It reads your dbt
model metadata (tables, columns, keys and relations) and writes a single portable
.html you can open straight from disk.
It is bun-native, and the interactive repo/project browser is rendered with
OpenTUI.
bun install # one-time: install deps
# from a local checkout of a dbt project
bun cli/index.js --path /path/to/dbt_project # writes <project>.erd.html
# or browse your dbt repos — yours, your orgs', and ones shared with you
bun cli/index.js # only repos with dbt; type to filter
bun cli/index.js --all # browse ALL accessible repos
bun cli/index.js --repo owner/name # clone + generate one repo
bun cli/index.js --repo owner/name --branch dev # a specific branch (else prompted)
# install the `dbt-erd` command globally
bun link && dbt-erd --path /path/to/dbt_projectOptions
| Flag | Description |
|---|---|
--path <dir> |
Use a local directory; skip GitHub. Picks the dbt project (prompts if several). |
--repo <owner/name> |
Shallow-clone a GitHub repo and generate. |
--branch <name> |
Diagram a specific branch. Without it, browse/--repo prompt you to pick (default branch first) on a TTY, else use the repo default. --path only records the currently checked-out branch (never checks out). |
--all |
Browse all accessible repos. By default browse shows only repos containing a dbt_project.yml (found via GitHub code search across you + your orgs + collaborator repos, including nested ones in monorepos). |
--lineage |
Force the lineage view (model dependency DAG) — headless override. Interactively you're asked instead (see below). |
--security |
Generate a security matrix instead of an ERD: objects × principals with privilege badges and provenance, from dbt grants config (manifest or YAML fallback). Writes <project>.security.html. |
--build |
Run dbt deps + dbt docs generate in the project first so the diagram has real warehouse column types (writes dbt's target/; needs dbt + your warehouse profile). Without it, dbt-erd is read-only and uses existing artifacts or the yml fallback. |
--out <file> |
Output path (default <project>.erd.html, or <project>.security.html with --security). |
--open |
Open the generated file when done. |
--keep |
Keep the temporary clone. |
In an interactive terminal, browse mode renders a keyboard-navigable OpenTUI list —
type to fuzzy-filter, ↑/↓ to move, Enter to select, Esc to cancel — and falls back to a
numbered text prompt when there is no TTY or raw mode is unavailable. By default it shows only
repos containing a dbt_project.yml (owned, organization, and collaborator/shared); pass --all
for every accessible repo. Your GitHub login
comes from gh api user (run gh auth login first) and is only needed for browse mode;
--path and --repo don't use it.
Where the diagram comes from
- dbt artifacts (preferred) —
target/manifest.jsonfor models, seeds, snapshots and sources (with columns), enriched with warehouse types fromtarget/catalog.json(dbt docs generate). Disabled andephemeralnodes are skipped. - No artifacts? Fallback —
dbt_project.yml+models/,seeds/,snapshots/YAML (columns + tests + constraints), withref()/source()scanned from the.sql. - Relations (FK ERD) come from
relationshipsdata tests and model-contractconstraints(primary_key,foreign_key,unique,not_null) — column-accurate, composite keys included. A column that is bothuniqueandnot_nullis treated as a PK. --lineageinstead wires tables byref()/source()dependencies, upstream → downstream.
After you pick a project, an interactive run asks what to generate — ERD vs lineage — showing each with its relation count, and only offers modes that produce output: a project with FK relationships shows ERD; if it has none but its models have ref()/source() dependencies it goes straight to lineage; if neither has edges you get a tables-only ERD. --lineage skips the prompt; a non-interactive run defaults to the FK ERD.
The generated page is the read-only canvas viewer: pan, zoom, Fit, and a dark/light
toggle. A Group by dropdown adaptively clusters the tables — pick Domain (business
area from model names), Layer (dbt stage), Folder, or Schema, and related tables
pack into labeled, colored blocks instead of a hairball; only dimensions with real structure
are offered, and None (the default) is the flat view. Wide tables collapse to the first
8 columns — click the ▾ +N more footer to expand one, or use Expand all / Collapse
all. When column types are missing (yml fallback, no catalog.json) it shows a hint to re-run
with --build. It is fully offline — all JS/CSS is inlined and the model is embedded in the file.
The CLI builds the embed template on first run; you can also build it explicitly:
bun run build:viewer # outputs dist-viewer/viewer.html (the ERD embed template)
bun run build:security # outputs dist-security/security.html (the security matrix template)Security matrix (--security) renders a read-only, offline objects × principals grid
instead of a diagram: rows are models/tables grouped by schema, columns are roles/users/groups,
cells show compact privilege badges (ALL rendered distinctly). Hover or click a cell for
provenance — which file (and line, for YAML) granted it. Grantless objects are flagged, and a
filter box narrows rows. Grants come from dbt's grants model config (manifest config.grants
or, without artifacts, schema.yml + dbt_project.yml +grants). The web app has the same
export: paste a schema containing GRANT/REVOKE statements and use Export → Security
matrix (HTML).
CREATE [OR REPLACE] [TEMPORARY | TRANSIENT] TABLE [IF NOT EXISTS] name ( ... )with quoted / backtick /[bracket]/schema.qualifiednames.- Inline column constraints:
PRIMARY KEY,NOT NULL,UNIQUE,REFERENCES other(col). - Table-level constraints:
PRIMARY KEY (...),UNIQUE (...),FOREIGN KEY (...) REFERENCES other(...),CONSTRAINT ... FOREIGN KEY .... ALTER TABLE x ADD [CONSTRAINT ...] FOREIGN KEY (...) REFERENCES y(...).- Line (
--,#) and block (/* */) comments are ignored.
Select BigQuery from the dialect dropdown to work with BigQuery SQL instead of DDL.
- Paste a raw
WITH … AS (…)query — noCREATE TABLEstatements needed. Each CTE becomes a table node in the diagram. - Column names are extracted from the
SELECTlist of each CTE (using aliases where present). - Relationships are inferred automatically from
FROMandJOINreferences: a CTE that reads from another CTE gets an edge between them, and references to base tables create stub nodes connected to the CTE. - Backtick-quoted three-part names (
project.dataset.table) are supported — the table portion is used as the node label.
- Vite — build + dev server.
- @dagrejs/dagre — layered auto-layout.
- Custom canvas renderer + SQL DDL parser (no heavy SQL-parser dependency).
| Key | Action |
|---|---|
| ⌘ / Ctrl + Enter | Re-arrange |
| Double-click canvas | Zoom in |
| Drag the pane divider | Resize the editor |
MIT © Royal Bhati for the original sqltoerdiagram. The dbt-erd additions in this fork are MIT too. Fork it, self-host it, point it at your dbt projects — go for it.