DVT DOCS
dvt docs generatebuilds your project's documentation catalog — the command you already know, invoked exactly as you've always invoked it. What's different is what ends up inside: DVT's catalog carries column metadata from every connection in your profile, collected natively from each engine, not just from the one warehouse your default target points at.
GENERATE IT
From inside your project:
$ dvt docs generate 14:32:09 DVT catalog: 38 relations cataloged across 4 connections, 51 nodes engine-stamped, DVT theme installed 14:32:09 Serve it with `dvt serve` (then open /catalog)
The compile logs you're used to stream first, then DVT's summary names the three things that just happened: how many relations got real column metadata, how many connections answered for them, and how many nodes were stamped with their engine so the lineage graph can color them. Selection flags you already use — --select, --exclude — are forwarded unchanged and work as you know them.
WHAT GENERATE ACTUALLY DOES
Four steps, in order. First the docs scaffolding is compiled — manifest and docs site, exactly as you know it (like every graph-bearing verb, it begins from a passing dvt parse). Then DVT takes over:
It stamps the graph with engines. Every model, seed, snapshot and source in the manifest gets a dvt_adapter_type; sources bound via meta.connectionalso carry their connection name. That's what lets the catalog's lineage graph color nodes by engine — you can see at a glance where a model's inputs actually live.
It collects column metadata from every connection, natively. Sources read from the engine their meta.connection points at; models materialized on a non-default target read from thattarget's engine. Collection is batched — one metadata query per connection and schema, never per table — so a connection carrying fifty sources costs one round trip, not fifty.
It installs the DVT-themed catalog UI in place of the stock one: dark, engine-aware, built to show a federated graph.
IN PLAIN DBT
dbt docs generatecatalogs exactly one warehouse — the catalog step queries the default adapter only, and a manifest that reaches into a second database is refused outright ("Cross-db references not allowed"). DVT builds the whole catalog itself instead: it asks every connection for its own metadata through that engine's native driver and assembles one catalog spanning all of them — Postgres beside Snowflake beside DuckDB, in the same lineage graph, colored by engine.
WHEN A CONNECTION CAN'T ANSWER — LOUD GAPS
A catalog that spans every connection has to decide what happens when one of them is down. DVT's answer: the catalog still builds, the gap is named, and nothing hangs. An unreachable engine gets a warning that says exactly what you'll be missing:
dvt docs: could not connect to snowflake — its relations will have NO columns in the catalog: 250001: Could not connect to Snowflake backend after 3 attempt(s)
Slow is handled the same way as down. Each metadata query gets 60 seconds, and each connection gets a 120-second budget across all its schemas — a connection that accepts the socket and then never answers is abandoned, with the reason stated ("did not answer within 60s" and "spent its whole 120s budget" are different facts, and the warning says which):
dvt docs: connection 'snowflake_prod' (snowflake) did not answer within 60s — abandoned; its relations will have NO columns in the catalog. Raise DVT_DOCS_CONNECTION_BUDGET / DVT_DOCS_QUERY_TIMEOUT if this engine is simply slow.
And an incomplete catalog says so in the summary itself — on stdout, not buried in a log:
14:32:09 DVT catalog: 29 relations cataloged across 3 connections, 51 nodes engine-stamped, DVT theme installed 14:32:09 1 connection(s) timed out and were abandoned (snowflake_prod) — their relations have no columns in this catalog 14:32:09 Serve it with `dvt serve` (then open /catalog)
The doctrine behind it: a catalog missing one engine beats a command that never returns, and incompleteness that announces itself is fine — silence is the sin. If an engine is genuinely slow rather than gone, raise the two environment knobs in the reference below and regenerate.
SERVING IT — DVT SERVE, THEN /CATALOG
The catalog is served by the suite, not by this command. There's no dvt docs serve — if you type it, DVT points you at the one way to serve:
$ dvt docs serve 'dvt docs serve' has moved — the suite serves every app: dvt serve (then open /catalog)
$ dvt serve ✅ DVT suite · 'analytics' · http://localhost:46100 http://localhost:46100/ hub + Settings http://localhost:46100/catalog catalog http://localhost:46100/portal api-portal http://localhost:46100/chat Chat with Martin http://localhost:46100/profiling profiling http://localhost:46100/scheduler scheduler http://localhost:46100/explorer explorer http://localhost:46100/dashboards dashboards http://localhost:46100/executions executions monitor Always up until `dvt kill`. Apps with no artifacts serve their empty state and pick up compiles on refresh.
One origin, every app a path on it — no separate docs port to remember, no terminal held hostage in the foreground. The order doesn't matter either: serve first and /catalog shows a waiting page that tells you to run dvt docs generate, then picks the catalog up on its own once it exists. Regenerate any time; the served page reflects it on refresh.
Like every DVT operation verb, dvt docs generatealso journals an execution record to the project's ledger — so the run, its outcome and its timing show up in the executions monitor at /executions alongside your runs and builds.
REFERENCE — EVERY FLAG
dvt docs generate:
| FLAG | DEFAULT | WHAT IT DOES |
|---|---|---|
| --no-compile | off | Build the catalog without recompiling first — reuse the existing manifest. |
| --static | off | Emit a single self-contained HTML file you can hand around without a server. |
| --project-dir | . | The project to catalog. Manifest, catalog and themed UI all land in its target/ directory. |
| --profiles-dir | (profiles.yml's usual home) | Read connections from a non-default profiles.yml location — the same connections metadata collection walks. |
Everything else — --select, --exclude, the selection surface you already know — is forwarded unchanged. Two environment variables bound the collection phase:
| ENVIRONMENT VARIABLE | DEFAULT | WHAT IT DOES |
|---|---|---|
| DVT_DOCS_QUERY_TIMEOUT | 60 | Seconds one metadata query may take. One query covers a whole connection+schema, so this is generous headroom, not a per-table clock. |
| DVT_DOCS_CONNECTION_BUDGET | 120 | Total wall-clock seconds one connection may spend across all its schemas before it's abandoned — no single engine can spend the whole command on its own. |