A semantic model tells dbt what a table means: which column identifies a row, which columns you can group or filter by, and which metrics it can compute. With that, dbt’s Semantic Layer writes the SQL for questions like “revenue by month by country”, and every tool that asks gets the same answer.
Since dbt 1.12 the definition lives next to the model, in the same YAML block as its columns. The older
semantic_models: format with measures still works, but dbt v2 warns about it; see
how to fix the “legacy YAML” warning.
Copy and paste
# models/marts/_semantic.yml
version: 2
models:
- name: orders
semantic_model:
enabled: true
agg_time_dimension: order_date
columns:
- name: order_id
entity:
type: primary
name: order
- name: customer_id
entity:
type: foreign
name: customer
- name: order_date
granularity: day
dimension:
type: time
- name: status
dimension:
type: categorical
metrics:
- name: revenue
label: Revenue
type: simple
agg: sum
expr: amount
- name: order_count
label: Orders
type: simple
agg: count
expr: 1
- name: customers
semantic_model:
enabled: true
columns:
- name: customer_id
entity:
type: primary
name: customer
- name: country
dimension:
type: categorical
The Semantic Layer also needs a daily time spine; see how to create a time spine model.
Validate and query on dbt platform:
dbt sl validate
dbt sl query --metrics revenue,order_count --group-by metric_time__month
Or locally with dbt Core and MetricFlow:
pip install "dbt-metricflow[dbt-snowflake]"
dbt parse
mf validate-configs
mf query --metrics revenue,order_count --group-by metric_time__month --order metric_time__month
metric_time__month revenue order_count
-------------------- --------- -------------
2026-01-01T00:00:00 200 2
2026-02-01T00:00:00 250 2
2026-03-01T00:00:00 180 2
The parts
semantic_model: enabled: trueturns the model into a semantic model.- Entities are the keys. The primary entity names what a row is (
order); a foreign entity (customer) is how the Semantic Layer joins to other semantic models. Becausecustomershascustomeras its primary entity,revenuecan be grouped bycustomer__countrywith no join written by you. - Dimensions are what you group and filter by. Categorical dimensions hold text or codes; time
dimensions need a
granularity, the finest grain of the column. agg_time_dimensionis the date that metrics on this model are counted by. It becomesmetric_time, sometric_time__monthgroups revenue by order month.- Metrics are the numbers. A
simplemetric aggregates one expression withagg:sum,count,count_distinct,average,min,maxand a few more.
Names in queries
Dimensions are addressed through their entity: order__status, customer__country. Time is grouped
with metric_time and a grain: metric_time__day, __week, __month, __quarter, __year. List
what a metric supports with:
mf list dimensions --metrics revenue
• customer__country
• metric_time
• order__order_date
• order__status
Next steps
- More metric types, such as ratios, running totals and growth: see how to define simple, ratio, cumulative and derived metrics.
- Querying and the common errors: see how to query a metric with dbt sl query.
Tested with dbt Core 1.12 and MetricFlow 0.213 on DuckDB; the dbt sl commands come from the dbt platform documentation.