A terminal UI for dbt model lineage — no compile, no browser.
dbtl renders your dbt project's dependency graph as ASCII art in a two-pane terminal UI. Pick a model on the left, see its lineage on the right, and answer "what does this model depend on, and what does it affect?" without leaving the terminal.
╭Seed────────────────────╮ ╭View───────────────────╮ ╭View────────────────────╮
│ source_datetime_policy │──▶│ stg_payment__shoppers │──▶│ int_shoppers__combined │
╰────────────────tests:4─╯ │╰───────────────tests:5─╯ ╰────────────────────────╯
│
╭Source────╮ │
│ shoppers │────────────────╯
╰──────────╯
Key features:
- No
dbt compilerequired. Builds lineage straight fromdbt_project.yml+ model.sql(ref()/source()) +schema.yml. Usestarget/manifest.jsonautomatically when present. - Single static binary, works over SSH. Everything happens inside the terminal — no
dbt docs serve, no browser. - Navigate the graph, not just the list. Move a cursor between lineage nodes, re-root on any model, filter by direction and depth, and walk your re-root history.
- Built for test-coverage and impact chores. Coverage/blast-radius/layer lenses (the layer lens also labels each column's layer along the pane border), a stats dashboard, untested-model cycling, and one-key export to Mermaid / Graphviz / Markdown impact reports.
- Trace a path, see a path. Move the lineage cursor and the root↔cursor path lights up — nodes AND the connectors between them — while everything off-path dims. A compact mode (
v) collapses boxes to one-row nodes for big graphs. - Diff two states of the project, then ship the review.
--diff <baseline>compares against another manifest or checkout: added models glow green and modified ones amber right in the lineage, a status chip counts+added ~modified -removed, andDopens a reviewer-shaped PR impact report — the changed nodes (with reasons), the aggregate downstream blast radius and affected marts, the affected exposures, a ready-to-rundbt build --select …command, and risk flags (untested changes, changed hubs, newly-introduced layer violations). Presseto export it as Markdown for the PR description,yto copy the build command. - Exposures answer "who cares". dbt exposures (dashboards, notebooks, …) render as terminator nodes on the right edge of the lineage — in both manifest and source mode — the impact chip gains an
exp:Ncount, and theiimpact report lists each affected exposure with its kind and owner.
Run it at the root of any dbt project:
$ dbtlThat's it. If target/manifest.json exists it is used; otherwise the project source is parsed directly.
Source mode infers lineage from the raw SQL (no Jinja rendering), so a compiled
manifest is always more accurate — if dbt is on your PATH, press P inside
the app to run dbt parse and switch to the generated manifest in place.
Homebrew (macOS / Linux):
$ brew install hytfjwr/tap/dbtlPrebuilt binaries: grab a tarball for your platform from the
releases page (checksums.txt
included).
From source (requires Rust 1.92+):
$ git clone https://github.com/hytfjwr/dbtl.git
$ cd dbtl
$ cargo install --path .# Auto-detect: reads ./target/manifest.json if present, otherwise parses ./ as source
$ dbtl
# Point at a project directory
$ dbtl --project /path/to/dbt-project
# Force a mode
$ dbtl --manifest /path/to/target/manifest.json
$ dbtl --source /path/to/dbt-project
# Select a model at startup and auto-reload on source changes
$ dbtl --select stg_orders --watch
# Pick a color theme (or set DBTL_THEME)
$ dbtl --theme ayu-mirage
$ dbtl --list-themes
# Diff against a baseline: "what does my branch change?"
$ dbtl --diff /path/to/main-checkout # another worktree (or its manifest.json)
$ dbtl --diff target/manifest.prod.json # a saved production manifest
# Generate static Markdown docs (no TUI)
$ dbtl docs --out ./dbt-docs # auto-detect the data source, write into ./dbt-docs
$ dbtl docs --manifest target/manifest.json --out docs/lineage
$ dbtl docs --source /path/to/project --out ./dbt-docs --quiet # no compile needed; CI-quietdbtl docs --out <DIR> is a non-interactive subcommand (it never starts the TUI,
so it runs fine in CI with no TTY). It writes a Markdown
documentation tree you can commit straight to a repo and read on GitHub/GitLab:
- One page per node (model / source / seed / snapshot / exposure): description, materialization, schema, tags, a column definition table (in dbt definition order), tests, direct + transitive upstream/downstream (as links), and a per-node Mermaid lineage diagram.
- An index
README.md: project summary, test-coverage % and orphan-model list, a table of every node (linked), and a whole-project Mermaid diagram (split by layer for large projects).
The data source is resolved exactly like the TUI (--manifest / --source / --project
/ auto-detect). Output is deterministic — regenerating from the same input is
byte-for-byte identical, so a CI job can dbtl docs … && git diff --exit-code to
assert the docs are up to date. Existing files are overwritten, never deleted.
The diff keys nodes by unique_id (a rename reads as removed + added — which is
what it is to every downstream ref()), and compares the surfaces a dbt change
actually moves: materialization, direct dependencies, columns, tests, and model
SQL. Two caveats: comparing raw SQL can't see a change inside a shared macro,
and a compiled-manifest baseline also contains installed packages' models, which
a source-parsed current side doesn't — for the cleanest signal, diff manifest
against manifest.
Press ? inside the app for the full list. Highlights:
| Key | Action |
|---|---|
j k h l |
Move in the list / move the lineage cursor |
Tab |
Switch focus between list ⇄ lineage |
Enter |
Structure view (columns / tests / description); on a lineage node, re-root to it |
/ |
Fuzzy search (list filter / lineage jump) |
u d [ ] 0 |
Filter the lineage view (direction / depth / reset) |
b f |
Back / forward in re-root history |
t |
Cycle lineage lens (test coverage → blast-radius heat → layer → layer violation → diff) |
v |
Toggle compact lineage (1-row nodes — fits ~2x more graph on screen) |
w |
Toggle whole-graph overview (the entire DAG in the lineage pane; Compact, minimap on) |
s / S |
SQL preview (syntax-highlighted) / project stats dashboard |
o |
Open the model's SQL in $EDITOR |
m x c |
Copy lineage as Mermaid / Graphviz DOT / ASCII art |
i / e |
Copy a Markdown impact report / write the diagram to a file |
! |
Copy the view as a runnable dbt build --select command |
Space ' |
Bookmark / cycle bookmarks |
T * |
Filter the list to untested / bookmarked models |
P |
Run dbt parse and switch to the compiled manifest |
D |
PR impact report vs the --diff baseline (then e exports it as Markdown, y copies the suggested dbt build command) |
Ctrl-p |
Command palette |
Ctrl-t |
Cycle the color theme |
q |
Quit |
Mouse is supported too: click a lineage node to re-root, wheel to scroll. The lineage pane pans on both axes — a horizontal wheel notch (trackpad swipe / tilt wheel) or Shift+wheel scrolls it sideways.
Built-in presets: default (xterm-256, works everywhere), ayu-dark, ayu-mirage, ayu-light, gruvbox-dark (truecolor). Pick one with --theme <name> (or the DBTL_THEME env var), cycle live with Ctrl-t, and list everything with --list-themes.
You can also define your own theme as a YAML file under ~/.config/dbtl/themes/ (or $XDG_CONFIG_HOME/dbtl/themes/) — the file name becomes the theme name:
# ~/.config/dbtl/themes/my-ayu.yml
base: ayu-mirage # optional preset to start from (default: "default")
colors: # override any role; the rest comes from the base
accent: "#ffcc66" # "#rrggbb" truecolor…
class_table: 114 # …or an xterm-256 indexRun dbtl --theme my-ayu. Unknown roles and bad colours fail with the valid alternatives listed; a palette that breaks the lens-visibility contract (e.g. a lens tint equal to a node colour) starts anyway but prints a warning.
Unicode box-drawing characters are East Asian Ambiguous width; some terminal configurations draw them 2 cells wide. dbtl probes the terminal at startup and falls back to pure ASCII (+ - | >) automatically, and you can force a mode:
$ dbtl --ascii # pure ASCII rendering
$ dbtl --unicode # skip the probe, keep Unicode box drawing