Column-Level Lineage in dbt for Metric Auditability
Track which source columns feed revenue metrics to catch dashboard breaks in minutes, not hours.

It's Friday afternoon. The CFO dashboard shows revenue down hard, and nobody knows why. This is the moment table-level lineage, the kind dbt gives you out of the box, runs out of road.
Why table-level lineage is insufficient for metric auditability
dbt's dependency graph tells you that fct_revenue depends on stg_orders. It does not tell you which columns in fct_revenue actually come from which columns in stg_orders. Dawiso's guide to dbt lineage breaks the problem into three levels of detail, table, column, and job, and points out that dbt natively covers only the first one. Everything past that, you build yourself.
That gap turns small questions into long afternoons. When a number on a dashboard looks wrong, table-level lineage hands you a list of models and leaves you to open the warehouse, read through SQL line by line, and dig through old Slack threads hoping someone remembers what changed. Atlan's lineage guide walks through exactly this kind of afternoon: revenue drops hard on a Friday, and the cause turns out to be a dbt model that quietly lost a join condition. Without column lineage, finding that takes hours. With it, the same fix takes minutes.
The question that matters, the one table-level lineage simply can't answer, is this: for the net_revenue field on this report, which source columns feed into it, and at what point in the pipeline did the transformation happen? Table-level lineage shrugs. Column-level lineage answers directly.
SQL parsing: the core mechanism behind column-level lineage
Getting an answer at the column level means reading the actual SELECT statements in your SQL and tracing how each column moves from one model to the next. dbt's own dependency graph is built only from ref() and source() calls, which define how models relate to each other and get written out to manifest.json. That graph is a useful starting point, but it stops at the model boundary. It has no idea what happens to individual fields inside the SELECT statement.
Most tools that solve this problem use the same basic approach: static SQL parsing. They take the compiled SQL sitting in dbt's own output files, manifest.json and catalog.json, and read through each SELECT expression to figure out which output column came from which input column. The shared library behind most of this work is sqlglot, an open-source SQL parser.
There's a second way to get the same answer: reading query logs after the fact, instead of parsing SQL before it runs. Static parsing works at compile time. It costs nothing to run and needs no warehouse access, though it can lose the thread on especially tangled SQL. Reading query logs after execution catches dynamic SQL that static parsing might miss, but it adds latency and needs warehouse-level access to pull those logs in the first place.
What comes out the other end looks nothing like a model-to-model diagram. Instead of [Model A](https://medium.com/@likkilaxminarayana/68-column-level-lineage-impact-analysis-in-dbt-a-complete-guide-f8dff44078ea) → Model B, a column lineage graph traces something like ModelA.customer_id → ModelB.customer_key → ModelC.customer_identifier, following one field's identity even as its name changes at every step. That's the resolution table-level lineage can never give you.
dbt Cloud's native column lineage coverage and who is locked out of it
dbt Cloud does offer column-level lineage natively, inside its Catalog product, but only to accounts on the Enterprise and Enterprise+ plans. Teams on other dbt Cloud tiers don't get it there at all. There's one partial exception: local column-level lineage is available to any user through the dbt VS Code extension running dbt v2.
This isn't a knock on dbt Labs, but most analytics teams aren't on Enterprise plans, which shapes which path is open to any given team. That access gap is the single biggest reason a whole ecosystem of outside tools has grown up to parse SQL, read dbt artifacts, and rebuild column-level lineage independently. Dawiso's guide lays out the native dbt Cloud path alongside this wider landscape of SQL-parsing alternatives, treating dbt Cloud Enterprise as one option among several rather than the only road in.
So the practical question for most teams reading this is which of the available paths fits their plan, their budget, and their engineering bandwidth. That's where the community has been busy.
Building column lineage without Enterprise access
dbt Core's community hasn't been sitting around waiting for a built-in answer. On a dbt-core GitHub discussion thread from April 2026, practitioners were openly building their own tooling, on the assumption that native column-level lineage in dbt-core isn't coming anytime soon.
A handful of tools have grown out of that gap, and each one takes a slightly different route to the same destination. lineage-lite is a small command-line tool written in Rust that statically analyzes SQL, compiled dbt models, and Python ETL scripts to build a column-level lineage map, all without needing dbt Cloud at all. Parrant takes a narrower approach: it reads only manifest.json and catalog.json, parses the compiled SQL with sqlglot, and never connects to the warehouse, so there's no credential to manage and no query cost to worry about.
Recce approaches the problem from a different angle entirely, folding column-level lineage into the pull request review process itself. As walked through in Dave Flynn's guide to the tool, Recce provides column-level lineage diff as part of a PR validation workflow, categorizing changes as a Model-Wide Change, a Column Change, or an Additive Change, formerly called breaking, partial-breaking, and non-breaking. In practice, the workflow runs in three steps. First, a lineage diff flags which models a given pull request could touch. Second, breaking change analysis strips out any changes that don't actually affect the data. Third, the column-level lineage view traces how a changed column ripples downstream, illustrated in the Jaffle Shop sample project by updating stg_payments.amount (say, removing a cents-to-dollars conversion) and watching every downstream model that inherits that column light up. That turns column lineage from something you check after a dashboard breaks into something that catches the break before it ships.
For teams with engineering time to spare but no appetite for license fees, OpenMetadata and DataHub both offer column-level lineage pulled from query logs, and both are free to use. The cost doesn't disappear, it just moves. Instead of paying a license fee, a team pays in the engineering hours needed to deploy the tool, keep it upgraded, and patch over whatever connectors don't quite fit their stack.
Column-level lineage as the foundation of trustworthy, auditable metrics
It's tempting to file all of this under "nice for debugging" and move on. That undersells the point. Column-level lineage matters most for proving to an auditor, a regulator, or a skeptical executive how one specific number got calculated.
That pressure is only building. SOX financial audits require a full, traceable calculation path for key metrics, so when an auditor asks how a revenue figure was derived, the honest answer runs through column-level lineage, layer by layer. Atlan's best practices guide names GDPR and CCPA compliance, SOX audits, and AI training data provenance as three separate drivers pushing teams toward column-level lineage, with root cause debugging as a fourth, more everyday reason that sits outside the compliance bucket entirely.
Independent research backs this up. A peer-reviewed study in the Journal of Electrical Systems lays out a compile-time method, built specifically around dbt, that processes compiled SQL and dbt artifacts into column-to-column edges, stores them in an open governance repository, and maps each one to a specific piece of control evidence. The study reports a meaningful drop in audit rework tickets per quarter, along with strong precision and recall on the lineage graph itself.
A case from the fintech world makes the stakes concrete. A Chief Risk Officer pulled the "fraudulent transaction count" for a daily report, and the number matched the analytics dashboard exactly, because the metric lived in one place: defined inside dbt. External auditors could trace that same definition straight through dbt and see the actual SQL that produced the figure.
It helps to separate two kinds of lineage that often get treated as the same thing. Technical lineage is operational. It exists to support debugging and impact analysis, and nobody needs it timestamped or locked down. Compliance lineage is auditable, with a documented, timestamped record of how a specific data element moved through specific systems over a specific window of time. Rerunning yesterday's question on compliance lineage needs to produce yesterday's answer, every time.
The dbt Semantic Layer and metric governance
Column lineage answers how a metric gets built. On its own, though, that's a map of one version of the truth, not a guarantee that it's the only version in circulation. The dbt Semantic Layer closes that loop: it takes the metric's derivation and makes it the single, version-controlled, official definition, so the number can't quietly drift apart across different dashboards, reports, and one-off queries.
With metrics defined in code, version-controlled, and documented the same way dbt models are, anyone can trace a metric straight back to the underlying data and see how it was built, start to finish. A single definition has to exist to trace back to. A global retailer working with B EYE learned this the hard way. Before any semantic layer got built, the engagement started with a metrics audit, simply cataloging every different definition of "revenue" already floating around the business. Column lineage can show how each version was calculated. It can't decide which one is correct, because without a single source of definition, column lineage alone cannot resolve conflicting metrics.
Sweetgreen's experience shows what happens once that single definition exists. Using AI tools layered on top of the dbt Semantic Layer, what used to mean emailing the data team and waiting two weeks for an answer turned into a 30-minute self-service task, so a business question that once sat in a queue for two weeks now gets answered the same afternoon.
dbt Labs open-sourced MetricFlow, the engine behind the Semantic Layer, under Apache 2.0, and in September 2025 helped launch the Open Semantic Interchange initiative alongside Snowflake, Salesforce, BlackRock, and RelationalAI. The goal is to make metric definitions portable across vendors, rather than locked to one company's tooling.
The BI-layer blind spot and cross-system gap
None of this solves everything, and it helps to be upfront about where the edges are. dbt only sees dbt. The pipeline that loads raw data into raw_orders and the BI report that reads from fct_orders both sit outside dbt's lineage graph entirely, and those two blind spots are where most real incidents start.
The BI side of that gap isn't a small oversight, it's structural. dbt can use exposures to note that a given report downstream depends on a model. What it can't do is show which specific columns that report pulls in, how those columns get renamed along the way, or how they get combined once they land on a dashboard. Closing that distance takes an explicit bridge between dbt's own graph and whatever semantic layer or BI tool sits downstream, because neither system knows on its own that model.finance.fct_orders.net_revenue model.finance.fct_orders.net_revenue is the same physical field that appears in a BI report under a completely different name.
Some platforms show what it looks like when that bridge actually gets built. Databricks Unity Catalog traces data all the way from raw ingestion, through dbt's transformations, out to whatever consumes it downstream, so a schema change at the source immediately appears in every dbt model and downstream asset it touches.
Atlan's best practices guide offers a useful way to think about where most teams actually stand. Covering lineage across every system a company uses, even shallowly, matters more at first than going deep on any single one of them. That same guide puts reactive, early-stage teams at typically covering less than 20% of their assets. A lot of teams that believe they've "solved" lineage have really just instrumented one system carefully, usually dbt, while the rest of the stack sits dark.
There's a simple test for where any team actually stands: can someone trace a number on a live dashboard all the way back to its original source system, without ever leaving the lineage tool? If the answer is no, the coverage isn't cross-system yet, no matter how good the dbt layer looks on its own.


