Use when creating or modifying dbt Semantic Layer components including semantic models, metrics, and dimensions leveraging MetricFlow.
Install with Tessl CLI
npx tessl i github:dbt-labs/dbt-agent-skills --skill building-dbt-semantic-layer82
Quality
78%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Optimize this skill with Tessl
npx tessl skill review --optimize ./skills/building-dbt-semantic-layer/SKILL.mdThis skill guides the creation and modification of dbt Semantic Layer components: semantic models, entities, dimensions, and metrics.
[!NOTE] This skill contains guidance for the new dbt semantic layer YAML spec, which requires dbt Core v1.12 or above or the dbt Fusion engine. If the user is using a different version of dbt, this guide will not help. Ask the user if they want to upgrade their dbt version and migrate to the new spec. If they do, you can use the semantic layer spec migration guide and dbt Fusion engine migration skill to assist.
Users may ask questions related to building metrics with the semantic layer in a few different ways. Here are the common entry points to look out for:
When the user describes a metric or analysis need (e.g., "I need to track customer lifetime value by segment"):
When the user specifies a model to expose (e.g., "Add semantic layer to customers model"):
Both paths converge on the same implementation workflow.
User asks to build the semantic layer for a project or models that are not specified. ("Build the semantic layer for my project")
Decide which dbt model to build the semantic model on. Add semantic_model: block to the model's YAML with enabled: true. Set agg_time_dimension to the primary time column. If the model does not have a time column, warn user that the model cannot contain metrics that are time-based. Ask the user if they want to create a derived time dimension.
Example YAML:
models:
- name: orders
semantic_model:
enabled: true # enable the semantic model
agg_time_dimension: ordered_at # set the primary time column (this is a column in the dbt model)Identify the primary key column (check for _id suffix, uniqueness tests, or explicit config). Add entity: \n\t type: primary block to that column's entry. If the model has foreign keys, define those as entity: type: foreign.
models:
- name: orders
semantic_model:
enabled: true # enable the semantic model
agg_time_dimension: ordered_at # set the primary time column (this is a column in the dbt model)
columns:
- name: order_id # this is the primary key column of the model
entity:
type: primary
name: order
- name: customer_id # this is a foreign key column of the model
entity:
type: foreign
name: customerScan columns for dimension candidates. These would be useful columns to group by when querying a metrics:
dimension: type: time with appropriate granularity (set at the column level)dimension: type: categoricalPresent suggested dimensions to user for confirmation.
Example YAML:
models:
- name: orders
semantic_model:
enabled: true # enable the semantic model
agg_time_dimension: ordered_at # set the primary time column (this is a column in the dbt model)
columns:
- name: order_id
entity:
type: primary
name: order
- name: customer_id
entity:
type: foreign
name: customer
- name: ordered_at
granularity: day # set the granularity of the time column
dimension:
type: time
- name: order_status
dimension:
type: categoricalCreate some simple metrics for the model. For each metric, collect: name, description, label, aggregation type, and expression. Support metric types: simple, derived, cumulative, conversion, ratio.
models:
- name: orders
semantic_model:
enabled: true # enable the semantic model
agg_time_dimension: ordered_at # set the primary time column (this is a column in the dbt model)
columns:
- name: order_id
entity:
type: primary
name: order
- name: customer_id
entity:
type: foreign
name: customer
- name: ordered_at
granularity: day # set the granularity of the time column
dimension:
type: time
- name: order_status
dimension:
type: categorical
metrics:
- name: order_count
type: simple
agg: count
expr: 1
- name: total_revenue
type: simple
agg: sum
expr: amount
- name: average_order_value
type: simple
agg: average
expr: amountAfter writing YAML, validate in two stages:
dbt parse to confirm YAML syntax and referencesdbt sl validate (dbt Cloud CLI / dbt Fusion)mf validate-configs (MetricFlow CLI)Do not consider work complete until both validations pass.
When modifying existing semantic layer config:
semantic_model: blockif the user wants to create a derived dimension or entity that is not a column within the dbt model, then we can use the derived_semantics block.
derived_semantics:
dimensions:
- name: order_size_bucket
type: categorical
expr: case when amount > 100 then 'large' else 'small' end
label: "Order Size"
entities:
- name: order_customer_key
type: foreign
expr: "order_id || '-' || customer_id"All simple metrics are defined at the model level under the metrics key. Advanced metrics that refer to simple metrics within the same model are defined within a model's YAML entry the models.metrics key. Advanced metrics that refer to simple metrics across different models are defined at the top level under the metrics key.
- name: revenue_per_order
type: derived
description: Average revenue per order
label: Revenue per Order
expr: total_revenue / total_orders
input_metrics:
- name: total_revenue
- name: total_orders
# With offset window
- name: revenue_growth
type: derived
expr: total_revenue - revenue_last_week
input_metrics:
- name: total_revenue
- name: total_revenue
alias: revenue_last_week
offset_window: 1 week
filter: "{{ Dimension('order__status') }} = 'completed'"- name: cumulative_revenue
type: cumulative
description: Running total of revenue
label: Cumulative Revenue
input_metric: total_revenue
grain_to_date: week
period_agg: first
# With window
- name: trailing_7d_revenue
type: cumulative
input_metric: total_revenue
window: 7 days- name: conversion_rate
type: ratio
description: Orders divided by visits
label: Conversion Rate
numerator: total_orders
denominator: total_visits
# With filters
- name: premium_conversion_rate
type: ratio
numerator:
name: total_orders
filter: "{{ Dimension('order__customer_segment') }} = 'premium'"
alias: premium_orders
denominator: total_visits- name: signup_to_purchase
type: conversion
description: Rate of signups converting to purchase
label: Signup to Purchase
entity: customer
calculation: conversion_rate
base_metric: signups
conversion_metric: purchases
window: 7 days
constant_properties:
- base_property: signup_channel
conversion_property: purchase_channel# For metrics depending on multiple semantic models
metrics:
- name: cross_model_ratio
type: ratio
numerator:
name: metric_from_model_a
filter: "{{ Dimension('entity__dim') }} > 10"
denominator:
name: metric_from_model_b
config:
group: example_group
tags:
- example_tag
meta:
owner: "@someone"Filters can be added to simple metrics or metric inputs to advanced metrics. The format of a filters is a Jinja template that can reference entities, dimensions, and metrics, a boolean operator, and a value. Ensure the value matches the type of the column being filtered.
Examples
filter: | {{ Entity('entity_name') }} = 'value'
filter: |
{{ Dimension('primary_entity__dimension_name') }} > 100
filter: |
{{ TimeDimension('time_dimension', 'granularity') }} > '2026-01-01'
filter: |
{{ Metric('metric_name', group_by=['entity_name']) }} > 100
semantic_model: block at model level with enabled: trueagg_time_dimension: at model level (not nested under semantic_model)entity: and dimension: on columns (can use shorthand or full form)granularity: required at column level for time dimensionsmetrics: array at model level for single-model metricsmetrics: key for cross-model metrics (derived, ratio, cumulative, conversion only)entity: primary instead of full nested form for simple caseslabel values for non-technical usersday)| Pitfall | Fix |
|---|---|
Missing agg_time_dimension | Every semantic model needs a default time dimension |
granularity inside dimension: block | Must be at column level |
| Defining a column as both an entity and a dimension | A column can only be one or the other |
Simple metrics in top-level metrics: | Top-level is only for cross-model metrics |
Using window and grain_to_date together | Cumulative metrics can only have one |
Missing input_metrics on derived metrics | Must list metrics used in expr |
65d2e0b
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.