Data / ML · Dbt

dbt Exposures and Lineage

dbt Exposures and Lineage is the work that turns raw data into decisions. The pipeline from "we have data" to "we have a model that runs in production" is the same in every.

John Kihiu12 min read

dbt's dependency graph naturally covers everything inside dbt — models depending on other models. Exposures extend that graph one hop further, into the things outside dbt that actually consume the data: a Looker dashboard, a Tableau workbook, an ML feature pipeline, an internal API. Without exposures, "what breaks if I change this column" stops at the last model; with them, it reaches all the way to the dashboard a VP checks every morning.

Declaring an exposure

An exposure is declared in YAML, usually its own exposures.yml, and references the models it depends on with ref() inside its depends_on — dbt treats it as another node in the DAG, just one that cannot be built, only depended on.

YAML · MODELS/EXPOSURES.YML
exposures:
  - name: executive_revenue_dashboard
    type: dashboard
    maturity: high
    url: https://looker.company.com/dashboards/42
    description: >
      Daily revenue and churn dashboard reviewed in the Monday
      leadership sync. Owned by the Finance data team.
    depends_on:
      - ref('fct_orders')
      - ref('fct_subscriptions')
    owner:
      name: Finance Data Team
      email: finance-data@company.com

Why this changes how you make a change

Before touching fct_orders, running dbt ls --select fct_orders+ shows every downstream model — but not the dashboard built on top of it. With the exposure declared, that same command surfaces executive_revenue_dashboard in the results, and the generated lineage graph in dbt docs draws a node for it. The practical effect: before merging a schema change, you can see exactly which dashboards and owners to notify, instead of finding out when someone in the Monday meeting asks why the number is wrong.

Exposures document intent, they do not enforce it

dbt does not verify that the dashboard actually still queries those columns — an exposure is a manually maintained declaration, not a live integration. If nobody updates exposures.yml when a dashboard is retired or rebuilt on different tables, the lineage graph quietly goes stale. Treat exposure maintenance as part of the same PR that changes the dashboard, not a separate housekeeping task.

Running lineage-aware selection with exposures in the graph

The `+` graph operator works the same way toward exposures as it does toward models, which is the main practical payoff:

SQL · SELECTING BY IMPACT
# everything downstream of fct_orders, including exposures
dbt ls --select fct_orders+

# only exposures affected by a given model
dbt ls --select fct_orders+ --resource-type exposure

# build only what an exposure needs, useful before a demo
dbt build --select +exposure:executive_revenue_dashboard

Where exposures fit next to a dedicated lineage tool

Exposures are cheap and require no extra infrastructure, but they only cover what someone manually declared, and only in the direction dbt already understands (models to consumers). A dedicated lineage platform consuming OpenLineage events can trace lineage automatically from ingestion tools through dbt through BI tools without manual YAML upkeep — worth it once you have enough dashboards that keeping `exposures.yml` current by hand becomes unreliable.

Exposure typeTypical use
dashboardLooker, Tableau, Mode reports
mlFeature store or model training pipeline
applicationInternal API or service reading warehouse tables
notebookRecurring analyst notebook, not ad hoc queries

Wrapping up

Add an exposure for anything a real person depends on regularly — a leadership dashboard, a billing pipeline, an ML feature set — so that changing an upstream model surfaces the actual blast radius before you merge, not after someone notices a broken chart. Keep the YAML current as dashboards change, and layer a dedicated lineage tool on top once manual upkeep stops scaling.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.