What you will be able to do
- Explain what an exposure is and what defining one gives you in the DAG and the docs site
- Write an exposure in YAML with its required, expected and optional properties
- Tell which exposure settings can and cannot be set in dbt_project.yml
- Select, run and test every resource upstream of an exposure
- Distinguish manual exposures from automatic Tableau exposures and their prerequisites
Key concept
Exposure — A node in the dbt DAG that stands for something outside the project that consumes its output, such as a dashboard, notebook or application. It has no SQL of its own. Its job is to name its upstream dependencies and the people who own it.
1.What an exposure adds to the DAG
A dbt DAG normally stops at the last model, but the people who depend on that model don't. A weekly revenue dashboard, a recommender notebook or a customer-facing app all read from tables dbt builds. Without exposures, nothing in the project records that they exist. An exposure is the node that records one of these consumers, so the DAG continues past fct_orders to the dashboard that reads it.
Declaring the consumer gives you two concrete things. First, the exposure becomes something you can select, so you can run, test and list everything that feeds it in one command. Second, it gets its own page in the generated documentation site, written for the people who use the data rather than the people who build it. If an analyst is about to change dim_customers, they can see that a high-maturity dashboard depends on it before they merge.
Exposures come in two kinds. Manual exposures are YAML you write and commit next to your models. Automatic exposures are created by dbt for supported BI integrations. They live in dbt's metadata system and appear in Catalog, the area of dbt, opened from its top-level navigation, where you browse a project's lineage and the metadata behind it. They behave like manual ones, but no YAML file exists for them. Most of this page is about manual exposures, because every project can use them. The last section covers the automatic route.
Checkpoint 1 of 8· Check yourself
Your team turns on automatic exposures for its Tableau workbooks. A colleague opens a pull request to edit one of those exposures' depends_on lists. What is wrong with that plan?
Automatic exposures behave like manual ones in Catalog, but dbt keeps them in its metadata system rather than in project files.
“These downstream exposures are stored in dbt’s metadata system, appear in Catalog, and behave like manual exposures. However, they don’t exist in YAML files.”Source: docs.getdbt.com
2.Writing an exposure in YAML
Manual exposures go under a top-level exposures: key in any properties file. The file can also declare models or sources, and it can sit at any depth under models/. Each exposure takes three required properties, one expected one, and a few optional ones that make it useful to readers. The smallest valid exposure fits on five lines:
- name: jaffle_wrapped
type: application
description: Tell users about their favorite jaffles of the year
depends_on: [ ref('fct_orders') ]
owner: { email: summer-intern@jaffleshop.com }The name is an identifier, not a title, so it can only contain letters, numbers and underscores. If the dashboard is called "Jaffles by the Week", that text goes in label, which allows spaces, capitals and special characters. The type must be one of five values: dashboard, notebook, analysis, ml or application. The docs site uses it to group exposures. The owner needs a name, an email, or both.
| Property | Status | What it does |
|---|---|---|
| name | Required | Unique name written in snake case; letters, numbers and underscores only |
| type | Required | One of dashboard, notebook, analysis, ml, application; organises the docs site |
| owner | Required | Who to contact; name or email must be given |
| depends_on | Expected | List of ref(), source() and metric() parents |
| label | Optional | Human-friendly title with spaces and capitals |
| url | Optional | Turns on the View this exposure link in the generated docs site |
| maturity | Optional | Confidence in the exposure: high, medium or low |
depends_on is the property that ties the exposure into the DAG, and it accepts ref(), source() and metric(). Pointing at a model is the normal case. An exposure can depend on a source directly, but that is rarely what you want, because a consumer usually reads a modelled table rather than raw data. This YAML list is not the -- depends_on comment you can put at the top of a model's SQL file. That directive forces a model to build after the resources it names. The exposure property only declares which nodes the consumer reads.
- name: jaffle_recommender
maturity: medium
type: ml
url: https://jupyter.org/mycoolalg
description: >
Deep learning to power personalized "Discover Sandwiches Weekly"maturity tells readers how much to trust the consumer. Use high for an established dashboard the whole company relies on and low for a new or experimental analysis. The url is what makes the exposure's docs page clickable, because it fills in the link back to the real dashboard or notebook.
Checkpoint 2 of 8· Check yourself
A new exposure is declared with name: Weekly Revenue Dashboard. What is the fix?
Exposure names are limited to letters, numbers and underscores. The label property exists to hold a human-friendly title.
“Exposure names must contain only letters, numbers, and underscores (no spaces or special characters).”Source: docs.getdbt.com
Checkpoint 3 of 8· Exam question
A team defines the following exposure in `models/marts/finance/_finance__models.yml`, but `dbt run -s +exposure:board_revenue_dashboard` builds nothing upstream of the exposure: ```yaml exposures: - name: board_revenue_dashboard type: dashboard owner: name: Finance Analytics depends_on: - fct_revenue - dim_date ``` Which line is the root cause of the missing upstream lineage?
Correct answer: A — The `depends_on` entries list bare model names instead of `ref('fct_revenue')` and `ref('dim_date')`, so dbt cannot resolve them as graph nodes.
- A. Correct. The `depends_on` list must contain `ref()`, `source()`, or `metric()` calls so dbt's parser can register a graph edge; a bare model name is just an unresolved string and produces no upstream dependency, so `+exposure:board_revenue_dashboard` has nothing to select.
- B. Incorrect. `url` is an optional field that only controls whether a clickable link shows up in the generated docs site; it has no effect on how dbt resolves `depends_on` entries into graph edges.
- C. Incorrect. `owner` only needs at least one of `name` or `email` to be valid, and `name` alone satisfies that requirement, so this block would not cause dbt to drop the exposure.
- D. Incorrect. `dashboard` is a fully supported exposure `type` and depending on marts models is the normal use case; the `type` value plays no role in resolving lineage.
3.What dbt_project.yml can and cannot set
Exposures don't follow the configuration rules you know from models. dbt treats exposure properties as special properties: you can't set them in dbt_project.yml or in a config() block, so each one goes on the exposure in its YAML file. With models, a whole folder can inherit +tags from [the project file](lesson:1.6). Exposures have no equivalent.
enabled is the one exception. You can set it at the project level with the usual + prefix, which turns all exposures on or off, and you can still override it on a single exposure. Tags and meta go on the exposure itself, under its config: block (they moved there in v1.10).
Checkpoint 4 of 8· Fill the gap
Complete the only exposure config that dbt_project.yml accepts.
exposures:
? : trueProject-level exposure configs use the + prefix, and enabled is the only one supported. Tags, meta and maturity have to be set on each exposure.
Source: docs.getdbt.comSources3
4.Running and testing what feeds an exposure
After you declare an exposure, you can answer "is the data behind this dashboard fresh and correct?" with one selector. The exposure: method selects an exposure's parent resources, and you use it with the + graph operator. +exposure:weekly_jaffle_report means "everything upstream of this exposure", which is exactly the set you rebuild or test before a dashboard refresh.
dbt run -s +exposure:weekly_jaffle_report
dbt test -s +exposure:weekly_jaffle_reportA wildcard widens the scope to every consumer. +exposure:* tests all resources upstream of all exposures, which suits a CI step that only guards what someone actually uses. Adding --resource-type source to a dbt ls with that selector lists only the raw source tables that consumers ultimately depend on. That list is useful to check against [source freshness](lesson:6.2). The resource_type:exposure method is another route: dbt build --select "resource_type:exposure" builds all resources upstream of exposures.
Checkpoint 5 of 8· Check yourself
Before a platform migration, you need the list of raw source tables that any dashboard, notebook or app ultimately reads. Which command produces it?
The + goes before the exposure selector to pick up its parents, and --resource-type source narrows the list to sources. Listing every source ignores whether any consumer uses it.
“list all source tables upstream of all exposures”Source: docs.getdbt.com
Checkpoint 6 of 8· Exam question
The analytics team just merged updated logic into `fct_orders` and `dim_customers`. Before the nightly BI dashboard exposure `weekly_jaffle_metrics` refreshes, they want to rebuild only that exposure's upstream models, without touching unrelated marts. Which command accomplishes this?
Correct answer: A — `dbt run -s +exposure:weekly_jaffle_metrics`
- A. Correct. The leading `+` selects the exposure plus every node upstream of it, so this rebuilds exactly the models the dashboard depends on without touching unrelated marts.
- B. Incorrect. A trailing `+` selects the exposure plus everything downstream of it. An exposure has no downstream dbt nodes, so this effectively only targets the exposure itself and skips the upstream models that actually need rebuilding.
- C. Incorrect. This treats `weekly_jaffle_metrics` as if it were a model name; since an exposure is not a buildable node, dbt cannot resolve this selector against the exposure at all.
- D. Incorrect. Selecting `exposure:weekly_jaffle_metrics` alone targets only the exposure node with no graph operator, and since exposures are not materialized, this builds nothing upstream.
Sources4
5.Automatic exposures from Tableau
Manual exposures go stale when someone builds a new dashboard and forgets the YAML. Automatic exposures fix this for Tableau: dbt reads the workbooks and creates the exposures itself, so lineage stays current without anyone editing files. The feature is part of dbt's Enterprise-tier plans and appears in Catalog.
Several prerequisites and limits decide whether a given workbook appears:
- A production deployment environment with at least one successful job run, on a supported release track - A Tableau personal access token whose creator has Connect permission, and the Metadata API enabled on Tableau Server - Only one Tableau site per server, and dbt's IP addresses allowlisted if you run Tableau Server - No dashboards built on custom SQL queries, because those aren't supported
Matching names cause most missing-lineage problems. dbt joins a workbook to models by fully qualified name, so {database}.{schema}.{name} in Tableau has to match the dbt build. Case doesn't matter. A workbook that reads analytics.dbt_data_team.my_model while production builds prod_analytics.dbt_data_team.my_model won't show all its dependencies. The fix is to change the Tableau FQNs to match dbt, not the reverse. Workbooks sync once a day, and also immediately when someone changes the selected collections.
To set it up, open the project's Settings and use Add lineage integration in the Exposures section. Enter the Tableau server URL, site name, token name and secret (all case-sensitive), then pick the collections to import. A collection is a group of Tableau workbooks: dbt imports every workbook inside the collections you select, so that choice decides which dashboards become exposures. In Catalog's Exposures table, an Exposure mode column shows Auto or Manual, so both kinds sit side by side. Orchestrating these exposures, which refreshes Tableau's data sources during scheduled dbt jobs, is a separate beta feature that runs in the dbt scheduler rather than in Catalog.
Checkpoint 7 of 8· Put it in order
Put the steps for connecting Tableau automatic exposures in order.
- 1.Under Exposures, select Add lineage integration
- 2.Select the collections to include and click Save
- 3.Enter the Tableau connection details and click Continue
- 4.Open the project's Settings in dbt
The integration is added from project Settings. You enter connection details before choosing which collections dbt imports and syncs.
“Select the collections you want to include for the downstream exposures and click Save.”Source: docs.getdbt.com
Checkpoint 8 of 8· Exam question
A new exposure fails `dbt parse` with an error stating that the exposure is missing owner information. Which of the following actions resolves the error?
Correct answer: A — Add `owner: { email: data-team@company.com }` to the exposure, since owner needs at least a name or an email.
- A. Correct. `owner` is a required property that must supply at least a `name` or an `email`; adding an email address alone satisfies that requirement and clears the parse error.
- B. Incorrect. `maturity` only records a confidence level (`high`, `medium`, or `low`) for documentation purposes and has no relationship to the separate, required `owner` property.
- C. Incorrect. `tags` are used for grouping and selection; dbt does not read a tag namespace to infer who owns an exposure, so the owner requirement remains unmet.
- D. Incorrect. `url` only links to the exposure's location for the docs site; dbt does not parse it to derive an owner, so this does not satisfy the required `owner` property.
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Exposure tags, meta or maturity can be set for all exposures with + configs in dbt_project.yml, as you would for models.Why is that wrong?
Exposure properties are special properties. enabled is the only exposure config dbt_project.yml supports, and everything else is set on the exposure in YAML.
Covered in What dbt_project.yml can and cannot set
2.An exposure's depends_on list is the same as the -- depends_on comment at the top of a model SQL file.Why is that wrong?
The SQL comment directive forces a model to build after the resources it names. The exposure's depends_on property only lists the nodes the downstream consumer reads.
Covered in Writing an exposure in YAML
3.The exposure: selector works on its own, without a graph operator, to run the models behind a dashboard.Why is that wrong?
The exposure method selects an exposure's parent resources and is meant to be used with the + operator, as in +exposure:weekly_kpis.
Covered in Running and testing what feeds an exposure
4.Different capitalisation between Tableau and dbt table names is why an automatic exposure is missing dependencies.Why is that wrong?
FQNs must match, but the comparison isn't case-sensitive. Look for a different database or schema name instead.
Covered in Automatic exposures from Tableau
Practise it for real
Declare a manual exposure for a dashboard and use it to select everything it depends on.
1.In a .yml file under models/, add an exposures: key with an exposure named weekly_jaffle_metrics, type: dashboard, an owner email, and depends_on listing ref('fct_orders').
Why: name, type and owner are required, and depends_on connects the exposure to the DAG.
You should see: The project parses without errors and the exposure is now a node in the DAG.
2.Run dbt ls --select "+exposure:weekly_jaffle_metrics".
Why: The exposure method combined with + selects the exposure's parents.
You should see: fct_orders and everything upstream of it are listed.
3.Run dbt ls --select "+exposure:*" --resource-type source.
Why: This narrows the selection to the raw tables that consumers ultimately read.
You should see: Only source tables upstream of your exposures are listed.
4.Run dbt test -s +exposure:weekly_jaffle_metrics.
Why: This tests only the data the dashboard depends on.
You should see: Tests on fct_orders and its parents run, and unrelated tests do not.
Stuck? Get a nudge
If parsing fails on the exposure, check that its name uses only letters, numbers and underscores.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.https://docs.getdbt.com/docs/build/exposuresOfficial docs
“run, test, and list resources that feed into your exposure”
↩︎ What an exposure adds to the DAG“populate a dedicated page in the auto-generated documentation site with context relevant to data consumers”
↩︎ What an exposure adds to the DAG“type: one of dashboard, notebook, analysis, ml, application (used to organize in docs site)”
↩︎ Writing an exposure in YAML“While possible, it is highly unlikely you will ever need an exposure to depend on a source directly.”
↩︎ Writing an exposure in YAML“url: Activates and populates the link to View this exposure in the upper right corner of the generated documentation site”
↩︎ Writing an exposure in YAML“define and describe a downstream use of your dbt project, such as in a dashboard, application, or data science pipeline”
↩︎ Key concept“Do not confuse this depends_on YAML property with the -- depends_on a SQL comment directive”
↩︎ Exam trap 2“These downstream exposures are stored in dbt’s metadata system, appear in Catalog, and behave like manual exposures. However, they don’t exist in YAML files.”
↩︎ Checkpoint - 2.
“Navigate to Catalog by selecting Catalog from the top-level navigation.”
↩︎ What an exposure adds to the DAG“Downstream exposures is available on all dbt Enterprise-tier plans.”
↩︎ Automatic exposures from Tableau - 3.
“You may define exposures in YAML files that also define sources or models.”
↩︎ Writing an exposure in YAML“Currently, only the enabled config is supported”
↩︎ What dbt_project.yml can and cannot set“'tags' and 'meta' changed to config in v1.10”
↩︎ What dbt_project.yml can and cannot set“Exposure properties are "special properties" in that you can't configure them in the dbt_project.yml file or using config() blocks.”
↩︎ Exam trap 1“Exposure names must contain only letters, numbers, and underscores (no spaces or special characters).”
↩︎ Checkpoint - 4.
“The exposure method is used to select parent resources of a specified exposure. Use in conjunction with the + operator.”
↩︎ Running and testing what feeds an exposure“build all resources upstream of exposures”
↩︎ Running and testing what feeds an exposure“The exposure method is used to select parent resources of a specified exposure. Use in conjunction with the + operator.”
↩︎ Exam trap 3“list all source tables upstream of all exposures”
↩︎ Checkpoint - 5.
“Tableau dashboards built using custom SQL queries aren't supported.”
↩︎ Automatic exposures from Tableau“Downstream exposures sync automatically once per day or when a user updates the selected collections.”
↩︎ Automatic exposures from Tableau“dbt automatically imports and syncs any workbook within the selected collections.”
↩︎ Automatic exposures from Tableau“To view all expected dependencies in your exposure, the FQNs must match but aren't case-sensitive.”
↩︎ Exam trap 4“Select the collections you want to include for the downstream exposures and click Save.”
↩︎ Checkpoint - 6.
“Proactively refreshes the underlying data sources during scheduled dbt jobs.”
↩︎ Automatic exposures from Tableau