Dataobservability
Blog / How to 8 min read

dbt Lineage Graph: How to Visualize End-to-End Lineage From Source to Dashboard

August 2026 · Dataobservability

SNOWFLAKE · PROD
247 tables |
Break a monitor:

Alerted #data-eng 0.8s ago.

Downstream impact · consumers at risk

INCIDENT #1042 OPEN · owner @you

Live console · pick a break, watch it get caught

A dbt lineage graph is the picture of how your models depend on each other, and dbt builds it for free from the ref and source functions in your SQL. You can see it three ways: locally with dbt docs generate and dbt docs serve, in dbt Catalog if you run dbt platform, or by reading the warehouse query history directly. Each one draws a different graph, and the differences matter most during an incident.

This article covers what each option actually shows, where the graph stops, and how to get from a broken source table to the dashboard a stakeholder is about to open. For a wider view of what dbt does and does not monitor, see our dbt observability comparison.

How can I visualize the data lineage created by dbt?

Run dbt docs generate then dbt docs serve, and open the graph icon in the bottom right of the docs site. That gives you an interactive DAG of every model, source, seed, snapshot and test in the project, built from the ref and source calls dbt parsed. If you run dbt platform, dbt Catalog renders the same graph without the local build step and adds column level lineage on Enterprise plans.

Both views are generated from the same artifact: manifest.json. Anything that ends up in the lineage graph got there because dbt could parse a ref or source out of your SQL. That single fact explains most of what follows, including every gap.

The three places a dbt lineage graph comes from

People use the phrase to mean at least three different artifacts. They are not interchangeable.

Where the graph comes fromWhat it showsWhat it costsWhere it stops
dbt docs (dbt docs generate) Model level DAG for the whole project, plus tests, sources, seeds and snapshots. Filterable with selectors like +model_name+ Free, in dbt Core Model level only. Static, so it reflects the last time you generated it, and somebody has to host it for the rest of the team
dbt Catalog (renamed from dbt Explorer) The same project graph kept current from your job runs, with search, model performance and column level lineage on the plans that include it Included with dbt platform. Column level lineage is Enterprise and Enterprise Plus only Still the project graph. Nothing dbt did not build is in it
Warehouse query history What actually ran against the warehouse, regardless of which tool wrote it. Snowflake ACCESS_HISTORY, Databricks Unity Catalog, BigQuery lineage Varies. Snowflake ACCESS_HISTORY needs Enterprise Edition or above No knowledge of your project structure, so it shows physical objects rather than models, and coverage differs by platform

The practical read: dbt docs and dbt Catalog answer "what does my project look like", and query history answers "what is actually happening in the warehouse". Incidents usually need both.

Does dbt docs show lineage, and is it enough?

Yes, and for a lot of teams it is genuinely enough. dbt docs generate compiles the project and writes manifest.json and catalog.json, and dbt docs serve starts a local site with a lineage graph you can click through. You can select a node and expand upstream and downstream, and the same selector syntax you use on the command line works in the graph, so +orders+ gives you everything feeding orders and everything that depends on it.

Two limits show up quickly. The first is that it is a static build, so the graph is only as current as the last person to run the command, and hosting it somewhere the analytics team can reach means a bucket, a CI step and somebody who owns it. The second is that it is model level. It will tell you that fct_orders depends on stg_payments. It will not tell you that only the refund_amount column does, which is the thing you need to know when one column breaks and you are deciding whether to wake anyone up.

dbt Catalog and column level lineage

dbt Catalog, which was renamed from dbt Explorer, keeps the graph current from your job runs and adds column level lineage on Enterprise and Enterprise Plus. There is no extra setup on a qualifying plan. It is a real improvement, and dbt documents its limits clearly enough that you should read them before depending on it.

Column level lineage reflects the lineage from select statements in your models and, in dbt's own words, does not reflect other usage like joins and filters. So a column that only ever appears in a join condition is doing real work and will not appear as a dependency. It relies on SQL parsing, and parsing fails in named cases: complex lateral joins are called out as ambiguous, so is JSON unpacking. Python models cannot be parsed at all, because the lineage of a Python model is not determinable from SQL, which makes it a hole in the graph rather than a node with edges. And hardcoded table names used instead of ref produce an unknown error, which in a project of any age is more common than anyone wants to admit.

That last one is worth a grep. Every hardcoded table reference in your project is a place where the lineage graph silently disconnects, and the models most likely to contain one are the oldest models, which are also the ones most likely to be feeding an executive dashboard. Our column level lineage page covers what each warehouse captures natively when the parser gives up.

What tools can help me understand the entire lineage from source to reporting in dbt?

dbt covers the middle. For the whole path you need three layers: something that captures ingestion before data lands in your sources, dbt Catalog or a warehouse level lineage reader for the transformation layer, and a BI integration to map the last model to the dashboards built on it. dbt sees only what it built, so end to end coverage always means joining its graph to metadata from either side of it.

Being concrete about the two edges is more useful than a tool list.

Where the dbt lineage graph ends, in both directions

Above your sources, dbt knows nothing. A source in dbt is a declaration that a table exists and is somebody else's problem. How rows got into it, whether the sync completed, whether a vendor changed a field name upstream, none of that is in the graph, and the incident is very often up there rather than in your SQL. If your raw layer is populated by a managed pipeline, that tool holds the lineage for the leg dbt cannot see, and the same is true if you connect apps, APIs and databases yourself to land data in the warehouse. Getting the whole picture means stitching that metadata to the top of the dbt graph, and the join key is usually the source table name.

Below your marts, dbt also knows nothing by default. The last model in the DAG is the end of the graph, and everything a person actually looks at lives past that point: Looker explores, Tableau workbooks, a Power BI semantic model, three scheduled email reports and a spreadsheet somebody built in 2023 that finance still uses. dbt exposures exist to close exactly this gap, and they are worth defining, because an exposure declared in YAML puts the downstream dashboard into the graph and into dbt build selectors. The catch is that exposures are manual. Somebody has to write and maintain them, and in practice they cover the dashboards a person remembered rather than the ones that exist.

Reading lineage from warehouse query history sidesteps both problems from a different angle, because a BI tool querying your mart leaves a trace whether or not anyone declared an exposure, and so does the ingestion job writing your raw tables. It also picks up the Python model, the hardcoded table name and the lateral join that the SQL parser could not resolve, because those all ran. What it loses is your project structure. The two approaches fail in opposite directions, which is why serious lineage tooling reads both.

Using the graph during an incident

The lineage graph earns its keep in about four minutes on a bad morning, and the sequence is always the same.

  • Start at the break, not the complaint. The report is "the revenue dashboard looks wrong". The useful first move is finding which upstream table changed, not tracing back node by node from the dashboard.
  • Walk downstream to decide who to tell. Column level lineage is what makes this fast. Table level says forty models are affected; column level usually says three are, and one of them is the executive dashboard.
  • Check the ends. If nothing in the project explains it, the cause is above your sources or the damage is below your last model. That is where most of the time gets lost, because neither end is in the dbt graph.
  • Write the exposure you wished existed. After the incident, add the exposure for the dashboard you had to find by asking around. That is how the downstream half of the graph gets built, one incident at a time.

The gap the graph cannot close

A lineage graph tells you what depends on what. It does not tell you that something broke. Those are different jobs, and it is worth being explicit about it, because teams frequently buy lineage expecting detection and get a very good map instead.

dbt's own detection is run scoped: tests execute during dbt test or dbt build, source freshness executes during dbt source freshness, and none of it runs between invocations. So a source that quietly stopped delivering, a job paused during a deploy, or a manual backfill that rewrote history produce no failing test, because no test ran. The graph is still perfectly accurate. It just has nothing to point at.

Pairing lineage with monitoring that watches the tables on its own schedule is what turns the map into an answer. When a freshness or volume break fires on a source table, the lineage graph immediately becomes the impact analysis: here is the column, here are the three models that use it, here is the dashboard at the end. Our data lineage diagram page shows what that looks like across a warehouse, and dbt data observability covers how monitors get generated from your manifest so you are not rebuilding the project inventory by hand.

If you want the column level detail specifically, including how each warehouse resolves it natively, dbt column level lineage goes deeper on the parsing rules and their failure modes.

Catch broken data before your stakeholders do

Connect your warehouse and get all five pillars monitoring from one read-only connection. Transparent pricing, no credit card.