Every Semantic Layer query runs in the warehouse. A dashboard opened by fifty people runs the same queries fifty times. Two kinds of caching cut that down: the warehouse’s own result cache, which needs no setup, and declarative caching, which pre-computes saved queries you choose and serves matching requests from a table.

Copy and paste

Declarative caching for a saved query (Enterprise plans):

saved_queries:
  - name: monthly_sales_by_country
    description: Revenue and orders by month and country.
    label: Monthly sales by country
    config:
      cache:
        enabled: true             # defaults to false
    query_params:
      metrics: [revenue, order_count]
      group_by:
        - TimeDimension('metric_time', 'month')
        - Dimension('customer__country')
    exports:                      # required: no export, no cache
      - name: monthly_sales_by_country
        config:
          export_as: table

Turn it on for every saved query in dbt_project.yml:

saved-queries:
  my_project:                 # your project's name from dbt_project.yml
    +cache:
      enabled: true

Then run the saved query in a deploy job with DBT_ENGINE_EXPORT_SAVED_QUERIES=TRUE, as for exports, and grant the Semantic Layer credential read access to the dbt_sl_cache schema.

Result caching

MetricFlow generates the same SQL for the same request, so the warehouse’s result cache works without any setup. On Snowflake, a repeated query within 24 hours, by the same user and on unchanged data, returns from the result cache. With a service token, every viewer’s query runs as the same warehouse user, which helps; with personal access tokens, each user has their own cache. BigQuery, Databricks and Redshift have their own rules; the dbt documentation links them.

Declarative caching

For saved queries with cache: enabled: true and an export, the deploy job builds a cache table in a dedicated dbt_sl_cache schema, with the deployment credentials. Afterwards:

Invalidation

dbt tracks when the models upstream of a cached saved query last received data. If a model has data newer than the cache, the cache is invalidated and queries go to the source tables until the next job rebuilds it. Stale cache tables are dropped periodically. The APIs can also invalidate the cache on demand.

So a cache never serves data older than its models, but it is only useful when the saved query runs after the models: put it at the end of the same job, or in a job triggered when the build finishes.

Access control

The dbt documentation is explicit: cached data is stored apart from the models, and queries served from the cache don’t carry the security context of the underlying tables. If different Semantic Layer credentials are meant to see different rows, don’t cache those metrics until dbt applies permissions to cache tables.

Requirements

Declarative caching needs an Enterprise or Enterprise+ plan, an environment on a release track (not a pinned legacy dbt version), a production environment with a successful run, and exports defined on the saved queries.

The project-level +cache setting was checked with dbt Core 1.12, where it set cache: enabled on the saved queries; the rest is based on the dbt platform documentation (October 2026).

References