CertSafari
    DBT Analytics Engineering· Lessons

    Domain 6 · Lesson 28/31

    dbt exposures: declaring downstream dashboards, apps and ML

    Implementing dbt exposures

    14 min read
    3.23% of exam
    6 sources
    Published 12 Oct 2026
    Docs as of 12 Oct 2026

    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?

    Sources12

    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:

    A minimal application exposure: name, type, description, one parent model, and an owner given only by emailyaml
      - 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.

    Exposure properties by how strongly dbt asks for them
    PropertyStatusWhat it does
    nameRequiredUnique name written in snake case; letters, numbers and underscores only
    typeRequiredOne of dashboard, notebook, analysis, ml, application; organises the docs site
    ownerRequiredWho to contact; name or email must be given
    depends_onExpectedList of ref(), source() and metric() parents
    labelOptionalHuman-friendly title with spaces and capitals
    urlOptionalTurns on the View this exposure link in the generated docs site
    maturityOptionalConfidence 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.

    An ml exposure that sets maturity and a url to the notebookyaml
      - 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?

    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?

    Sources13

    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:
       ? : true

    Sources3

    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.

    Rebuild and test everything upstream of one exposurebash
    dbt run -s +exposure:weekly_jaffle_report
    dbt test -s +exposure:weekly_jaffle_report

    A 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?

    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?

    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. 1.Under Exposures, select Add lineage integration
    2. 2.Select the collections to include and click Save
    3. 3.Enter the Tableau connection details and click Continue
    4. 4.Open the project's Settings in dbt

    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?

    Sources256

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 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. 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. 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. 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. 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. 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. 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. 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. 1.
      “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. 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. 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. 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. 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. 6.
      “Proactively refreshes the underlying data sources during scheduled dbt jobs.”
      ↩︎ Automatic exposures from Tableau

    Ready to test yourself?

    Practise the 11 questions on this subdomain.

    Spotted a mistake, or was something unclear? Tell us.