The recurring failure mode in analytics teams isn't a lack of dashboards, it's dashboards that disagree. Two people query "active users" from two different tools and get two different numbers because one filtered on a login event and the other on a session start. A metrics layer exists to make that impossible by moving the metric definition out of individual queries and dashboards and into one place that everything else references.
What a metric actually is in dbt's semantic layer
A dbt metric is defined against a semantic model — itself built on top of a dbt model — specifying an aggregation type (sum, count_distinct, average) and the column or expression it applies to, along with the dimensions it can be sliced by. Once defined, that metric can be queried through MetricFlow (dbt's query engine for the semantic layer) from the dbt Cloud CLI, from a BI tool with a supported integration, or via the semantic layer API, and every consumer gets the identical calculation.
semantic_models:
- name: orders
model: ref('fct_orders')
measures:
- name: order_total
agg: sum
expr: amount
dimensions:
- name: order_date
type: time
metrics:
- name: revenue
type: simple
type_params:
measure: order_total
filter: "{{ Dimension('order__status') }} = 'completed'"
Why this beats defining metrics inside the BI tool
Most BI tools let you define a calculated metric inside their own layer, but that definition only applies to charts built in that tool. The moment a second BI tool, a Python notebook, or a reverse-ETL job needs "revenue," it either re-implements the logic (drift risk) or queries the BI tool's API in a roundabout way. A metrics layer sitting in the transformation layer, ahead of any specific BI tool, means the definition is shared by construction rather than by discipline.
Dimensions let you slice without rewriting the underlying query
Because a metric is defined with its available dimensions, asking for "revenue by region" or "revenue by product category" doesn't require a new SQL query to be written — MetricFlow generates the appropriate grouped query from the semantic model. This is the practical payoff for analysts: the metric logic (what counts as revenue, what's excluded) stays centrally defined while the slicing stays flexible.
If the semantic model is built on a fct_orders table that itself has ambiguous grain (one row per order, or one row per line item — inconsistently), the metrics layer will faithfully compute wrong numbers with total consistency across every tool. The metrics layer guarantees agreement, not correctness; the underlying model still has to be right.
Adoption is incremental, not a rip-and-replace
You don't need every metric in the org defined in the semantic layer on day one. Start with the two or three metrics that cause the most cross-team disagreement — revenue, active users, churn — get those centrally defined and adopted by the loudest dashboards, and expand from there. Trying to model the entire metric catalog up front before shipping anything is the more common way these projects stall.
A metric definition that references a column renamed in a model change needs to fail CI, not silently return null or a wrong aggregate in production. Treat semantic model changes with the same review rigor as the models feeding them.
Wrapping up
A metrics layer's value isn't the query engine, it's the guarantee that "revenue" means the same thing whether it's queried from a dashboard, a notebook, or an API call. Start with the handful of metrics that actually cause disagreement across teams, define them once against a semantic model you trust, and let every consumer query that definition instead of re-deriving it.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.