What Is a Semantic Layer? A Practical Guide for Data Engineers

A semantic layer defines business metrics once so every consumer gets the same answer. Here's what it is, how it works, and how to build one with code examples.

20 min read

Your data warehouse has a table called orders. It has columns like amount, status, created_at, and customer_id. Now three people ask "What was Q1 revenue?"

The analyst writes SELECT SUM(amount) FROM orders WHERE created_at BETWEEN '2026-01-01' AND '2026-03-31'. The data engineer adds WHERE status = 'completed'. Finance excludes refunds and trial conversions. Three queries, three numbers, one question. Nobody is wrong. They just defined "revenue" differently.

Multiply this by every metric in your organization, every team that queries the warehouse, and every tool that displays a number. That's the problem.

A semantic layer solves it by defining each metric once, in one place, and serving that definition to every consumer.

What is a semantic layer?

A semantic layer is a metadata layer between your data warehouse and every tool that queries it. It defines business metrics, maps them to SQL, and exposes them through APIs. Instead of every consumer writing its own query, they all reference the same definition.

When someone asks for "revenue," the semantic layer knows that means:

SUM(CASE WHEN status != 'refunded' AND type != 'trial' THEN amount ELSE 0 END)

That definition lives in one place. Dashboards, APIs, AI agents, and ad-hoc queries all use it. Change the definition once and every consumer gets the updated calculation. No Slack thread asking "which number is right." No detective work tracing a wrong number back to a stale query in a notebook somewhere.

The core components of a semantic layer:

  • Metrics (measures). The numbers you aggregate: revenue, order count, average deal size. Each metric has a fixed SQL definition.
  • Dimensions. The columns you filter and group by: date, status, category, region. Dimensions define the axes of analysis.
  • Relationships (joins). How tables connect: orders belong to customers, products belong to categories. Defined once, reused by every query.
  • Access rules. Who can see what. Row-level security, tenant isolation, role-based access. Enforced on every query, not bolted on per tool.
  • Caching rules. Which rollups to pre-compute, how often to refresh, when to invalidate. Performance is part of the definition.

A brief history

The idea of abstracting business logic from raw data isn't new.

In the 1990s, Business Objects introduced "universes," metadata layers that mapped business terms to database columns. Analysts queried "Revenue by Region" without knowing the underlying table structure. It worked, but only within Business Objects.

SSAS (SQL Server Analysis Services) cubes brought multidimensional modeling to the Microsoft stack. You defined measures and dimensions in a cube, and every Excel pivot table and SSRS report consumed the same definitions. Powerful, but tightly coupled to the Microsoft ecosystem.

Looker's LookML (2012) moved semantic modeling into code. Metric definitions lived in version-controlled files, not a GUI. This was a significant shift: data teams could review metric changes in pull requests, not point-and-click editors. But LookML was (and is) proprietary to Looker, which is now proprietary to Google Cloud.

dbt's MetricFlow (2023) brought metric definitions into the dbt ecosystem. Define metrics alongside your transformations. Good idea, but MetricFlow defines metrics without serving them: no caching, no multi-tenancy, no API layer.

The current generation of semantic layers (Cube, AtScale, Bonnard) are standalone infrastructure. They connect to any warehouse, serve any consumer, and run independently of your BI tool. The semantic layer is no longer a feature inside a product. It's a layer in the stack.

What's driving this shift: the number of consumers has exploded. In 2015, a semantic layer served dashboards. In 2026, it serves dashboards, embedded analytics in customer-facing products, REST APIs, React components, TypeScript SDKs, markdown reports, and AI agents via MCP. One definition needs to serve all of them. A BI-embedded semantic layer can't do that.

Why does it matter?

Without a semantic layer, business logic lives in three places: your analysts' heads, scattered SQL files, and buried dashboard definitions. Each copy drifts over time. Someone updates the revenue definition in Looker but forgets the dbt model. A new analyst writes a fresh query from scratch and gets a number that doesn't match either.

The cost isn't abstract:

Inconsistent metrics. Marketing reports 12% growth. Finance reports 8%. The board meeting stalls while someone debugs which number is right. This happens every quarter at companies without a shared metric layer.

Duplicated logic. Every new dashboard, API endpoint, or report re-implements the same calculations. You end up with revenue defined in 14 places, each slightly different, each maintained by a different person. One team discovers a bug in their version and fixes it. The other 13 don't.

Slow onboarding. New data engineers spend weeks learning which queries are canonical and which are stale. "Ask Sarah, she knows which revenue query is the right one." Tribal knowledge doesn't scale.

The data team becomes a service desk. Instead of building, they spend their time answering ad-hoc questions: "Can you pull revenue by region for Q4?" "Which dashboard has the right churn number?" "Why does this report show different numbers than that one?" A semantic layer makes the data self-serve. The team defines the metrics. Everyone else queries them.

AI agents make it worse. An AI agent with warehouse access generates plausible SQL from column names. It doesn't know your business rules. Ask two different agents the same question and you'll get two different numbers. Neither matches finance. Text-to-SQL gives you speed without trust. A semantic layer gives you both.

Types of semantic layers

Not all semantic layers work the same way. The architecture matters because it determines what consumers you can serve, how fast queries run, and how much you control.

Embedded semantic layers live inside a BI tool. Looker's LookML, Tableau's semantic model, Power BI's DAX measures. They define metrics well, but only for that tool's consumers. Your Looker semantic layer doesn't help your React app or your AI agent. If you're in one BI tool and staying there, this can be enough. Most teams outgrow it.

Virtual semantic layers sit between the warehouse and consumers without moving data. AtScale is the primary example. Queries pass through the semantic layer, which translates them to the right SQL dialect and sends them to the warehouse. No data duplication. The tradeoff: every query hits the warehouse, so latency depends on warehouse performance. Virtual layers add caching to mitigate this, but it's an optimization, not the default.

Materialized semantic layers pre-compute rollups and store them separately. Cube's pre-aggregation engine is the best-known example. You define which combinations of measures and dimensions to pre-compute, and the semantic layer builds and maintains those materialized tables. Hot queries hit the cache (single-digit milliseconds). Cold queries fall through to the warehouse. This is how you get sub-second performance at scale.

Hybrid semantic layers combine virtual and materialized approaches. Most modern semantic layers work this way. Define pre-aggregations for your most-queried metrics. Let everything else pass through to the warehouse. You configure the tradeoff per metric based on query frequency and latency requirements.

For most teams, hybrid is the right choice. You get the flexibility of virtual queries with the performance of materialized caching where it matters.

How it works in practice

Modern semantic layers are defined in code (typically YAML), version-controlled in Git, and deployed as an API. Here's what that looks like end to end.

Define your metrics

cubes:
  - name: orders
    sql_table: public.orders
    measures:
      - name: total_revenue
        sql: "CASE WHEN status != 'refunded' AND type != 'trial' THEN amount ELSE 0 END"
        type: sum
      - name: order_count
        type: count
      - name: average_order_value
        sql: amount
        type: avg
    dimensions:
      - name: status
        sql: status
        type: string
      - name: category
        sql: category
        type: string
      - name: created_at
        sql: created_at
        type: time
      - name: customer_id
        sql: customer_id
        type: string

Measures are the numbers you aggregate: revenue, count, average. Dimensions are the columns you filter and group by: status, date, category. total_revenue is now a governed definition, not a column someone interprets.

You define joins between cubes to model relationships:

cubes:
  - name: customers
    sql_table: public.customers
    joins:
      - name: orders
        relationship: one_to_many
        sql: "{customers.id} = {orders.customer_id}"
    measures:
      - name: count
        type: count
    dimensions:
      - name: plan
        sql: plan
        type: string
      - name: signed_up_at
        sql: created_at
        type: time

Now any consumer can query "revenue by customer plan" without knowing how the tables join. The semantic layer handles the SQL generation.

Add caching with pre-aggregations

Queries against raw warehouse tables are expensive, especially when AI agents are querying them frequently. A pre-aggregation caches rollups so consumers get sub-second responses instead of waiting for full table scans.

    pre_aggregations:
      - name: daily_revenue
        measures:
          - total_revenue
          - order_count
        dimensions:
          - status
        time_dimension: created_at
        granularity: day
        refresh_key:
          every: 1 hour

The semantic layer rebuilds this rollup every hour. Hot queries hit the cache. Cold queries fall through to the warehouse. You configure the tradeoff per metric: high-traffic metrics get aggressive caching, low-traffic metrics query the warehouse directly.

Define access control

For B2B products serving multiple customers, every query needs to be scoped to the right tenant. A security context enforces this structurally, not through prompt engineering or middleware hacks.

    security_context:
      - name: tenant_filter
        sql: "{SECURITY_CONTEXT.tenant_id} = customer_id"

Every query for a given tenant automatically includes this filter. It's not optional. The consumer can't skip it or override it. This is how RBAC works in a semantic layer: defined in the schema, enforced on every query. It doesn't matter if the consumer is a dashboard, an API call, or an AI agent. The access rules are the same.

Expose to consumers

bon deploy

One deployment, multiple surfaces. The same metric definitions serve:

  • BI dashboards querying via REST API
  • React components rendering charts with @bonnard/react (BarChart, LineChart, BigValue, DashboardViewer, BonnardChart, and more)
  • AI agents discovering and querying metrics via MCP
  • Backend services calling the TypeScript SDK
  • Markdown dashboards authored in plain text with embedded queries

A dashboard asks for revenue by month. An AI agent asks for order count by status. A React component renders a chart. They all hit the same definitions and get the same numbers. That's the point.

Real-world use cases

Customer-facing analytics in a B2B SaaS product

You're building a SaaS product and your customers want to see their data. Usage metrics, billing summaries, performance dashboards. Without a semantic layer, you build custom API endpoints for each view, maintain the SQL yourself, and hope the numbers match what your internal dashboards show.

With a semantic layer: define the metrics once, generate a publishable key per tenant with row-level security, and let customers query their data through your product's UI (via the React SDK) or through their own AI agents (via MCP). Multi-tenancy is structural. The same metric definition serves every customer with automatic data isolation.

Replacing internal dashboards nobody opens

Your team built 47 Metabase dashboards. Three of them get used. The rest are stale, broken, or duplicated. The data team spends time maintaining dashboard infrastructure instead of defining the metrics that matter.

A semantic layer shifts the model: the data team defines and governs metrics. Consumers choose their own surface. Some use dashboards. Some query the API. Some ask an AI agent. The data team's job is the definitions, not the delivery mechanism.

Connecting AI agents to governed data

Your engineering team wants to ship an AI-powered analytics feature. The first prototype uses text-to-SQL. It works in demos and breaks in production: inconsistent numbers, no tenant isolation, no audit trail. The fix isn't better prompts. It's a semantic layer. The agent queries metric definitions instead of raw tables. Every answer traces back to a versioned, governed definition. Full walkthrough: How to Connect an AI Agent to Your Data Warehouse.

Semantic layer vs. dbt metrics

This is the most common question in the space, and the answer is: they're complementary, not competing. But the differences matter.

dbt defines transformations: how raw data becomes clean tables. It runs SQL models that create dim_customers, fct_orders, and other modeled tables in your warehouse. This is the "T" in ELT. dbt is excellent at this.

dbt's MetricFlow adds metric definitions on top of those tables. You can define revenue as a metric in your dbt project. This is genuinely useful for documentation and consistency within dbt's ecosystem.

Where dbt stops: MetricFlow defines metrics but doesn't serve them to production consumers. There's no built-in:

  • Caching or pre-aggregation. Every query hits the warehouse.
  • Multi-tenancy. No per-tenant isolation for B2B use cases.
  • API serving. No REST API or SDK for applications to query.
  • AI agent integration. No MCP server or tool-use interface.
  • Row-level security. Access control is warehouse-level, not metric-level.

A standalone semantic layer fills these gaps. It connects to the same warehouse where dbt outputs its modeled tables, defines metrics on top, and serves them with caching, multi-tenancy, and multiple APIs.

The practical workflow: dbt transforms your raw data into clean tables on a schedule. The semantic layer defines business metrics on those tables and serves them to dashboards, applications, and AI agents in real time. Most semantic layers, including Bonnard, can import dbt models directly. You don't choose between them. You use both.

Semantic layer tools compared

Several tools occupy this space, each with different design priorities.

Tool Approach Strengths Limitations
Cube Open-source semantic layer engine Battle-tested query engine, pre-aggregation, REST API, large community No built-in MCP, dashboards, or multi-tenant keys. You build the product layer yourself.
dbt Semantic Layer / MetricFlow Metric definitions in dbt Tight dbt integration, SQL-native, good for documentation Defines metrics but doesn't serve them. No caching, no multi-tenancy, no agent support.
Looker (LookML) BI-embedded semantic layer Mature modeling language, strong governance, Google Cloud integration Expensive, enterprise-only, proprietary. Definitions locked to Looker's ecosystem. No MCP.
AtScale Virtual semantic layer Enterprise BI compatibility, no data movement, MDX/DAX support Enterprise pricing. Not agent-native. Complex deployment.
Power BI (DAX) BI-embedded semantic layer Deep Microsoft integration, familiar to analysts Locked to Power BI ecosystem. No API serving. No multi-tenancy for embedded use cases.
Tableau (Semantic Model) BI-embedded semantic layer Visual modeling, strong visualization Definitions don't leave Tableau. No API access. Salesforce pricing.
Bonnard Agent-native semantic layer built on Cube MCP + React SDK + dashboards + REST API from one schema. Multi-tenant keys. Open source (Apache 2.0). Newer product. Smaller community.

The right choice depends on your primary consumer. If internal dashboards are the only output and you're already in Looker or Power BI, the embedded semantic layer may be sufficient. If you're serving multiple consumers (customer-facing products, AI agents, APIs, internal dashboards), you need a standalone semantic layer that isn't locked to one tool.

Warehouse-specific considerations

Your semantic layer needs to work with your warehouse. The integration isn't just "can it connect." It's how well the semantic layer exploits each warehouse's strengths.

Snowflake. Pre-aggregations work well with Snowflake's auto-suspend compute. The semantic layer can target specific virtual warehouses for different workloads: fast cache rebuilds on a small warehouse, heavy ad-hoc queries on a larger one. Snowflake's separation of storage and compute means pre-aggregation tables don't compete with production workloads.

BigQuery. BigQuery's slot-based pricing means pre-aggregations save real money. Every query scans data and costs money. Caching a daily revenue rollup avoids scanning terabytes of raw data on every request. The semantic layer's pre-aggregation layer can cut BigQuery costs by 10-100x for frequently queried metrics.

Databricks. Unity Catalog provides table-level governance. The semantic layer adds metric-level governance on top: defining what "revenue" means, not just who can access the orders table. Databricks' Photon engine handles the heavy queries. The semantic layer caches the results.

PostgreSQL (including Supabase, Neon, and RDS). Works well for smaller datasets and development. Most teams start with Postgres for prototyping their semantic layer and migrate to a cloud warehouse as data volume grows. Pre-aggregation is especially valuable here because Postgres doesn't have the raw compute power of cloud warehouses for large analytical queries.

DuckDB (including MotherDuck). In-process analytical database. Useful for local development and testing without a remote warehouse connection. Define your metrics, test locally against DuckDB, deploy to production against Snowflake or BigQuery. Same schema, different warehouses.

How to evaluate a semantic layer

If you're choosing a semantic layer for your stack, here's what to look at:

Who are your consumers? This is the most important question. If it's only internal dashboards, an embedded semantic layer in your BI tool might be enough. If it's dashboards plus a customer-facing product plus AI agents, you need a standalone semantic layer with API serving.

Does it support your warehouse? Check for native support, not just "we can connect via JDBC." Native support means the semantic layer generates optimized SQL for your specific warehouse dialect and exploits warehouse-specific features like Snowflake's clustering or BigQuery's partitioning.

How does it handle caching? Pre-aggregation support varies widely. Some tools don't cache at all (every query hits the warehouse). Some cache at the query level. The best cache at the metric level with configurable refresh schedules and automatic invalidation. If AI agents are querying your data, caching is not optional.

Multi-tenancy. If you're building a B2B product, every query needs tenant isolation. Look for structural multi-tenancy (security context in the schema), not middleware-level filtering. One missed filter is a data leak.

Schema-as-code. Metric definitions should live in version control. Changes should go through pull requests. Rollbacks should be git revert, not clicking through a UI. If you can't diff your metric definitions, you can't govern them.

AI agent support. If AI agents are current or future consumers, look for MCP support or a tool-use interface. An API is the minimum. MCP is the emerging standard for agent-to-tool communication and gives agents native discovery of available metrics.

When do you need a semantic layer?

Not every team needs one. Here's when it starts to matter.

You need one when:

  • Multiple consumers query the same metrics (dashboards + API + agents)
  • Different teams report different numbers for the same question
  • You're shipping analytics to customers (B2B embedded analytics)
  • AI agents need access to your data with governance
  • New analysts spend days learning which queries are canonical
  • Your data team is stuck answering ad-hoc metric questions instead of building

You probably don't need one when:

  • One analyst queries the warehouse with SQL and shares results in spreadsheets
  • You have a single dashboard tool and no API consumers
  • Your data model is small enough that everyone knows the canonical queries

The threshold is usually "more than one consumer for the same metrics." Once dashboards, APIs, and applications all need revenue numbers, a shared definition pays for itself immediately.

Getting started

The fastest path from zero to a working semantic layer:

Cloud:

npm install -g @bonnard/cli
bon init
bon deploy

Self-hosted:

npm install -g @bonnard/cli
npx @bonnard/cli init --self-hosted
docker compose up -d
bon deploy

bon init also generates agent configs (rules and skills for Claude Code, Cursor, and Codex) so AI coding assistants understand your semantic layer from the first prompt. Define your cubes, then use Claude Code with the /bonnard-get-started skill to walk through configuration. Run bon mcp to output MCP connection configs for your agents. Use bon diff to preview changes before deploying, bon schema to explore deployed measures and dimensions, and bon docs to browse documentation from the CLI. The full tutorial with working code: How to Connect an AI Agent to Your Data Warehouse.

If you're evaluating options, start with what your primary consumer needs. Internal dashboards only? Your BI tool's built-in semantic layer may be enough. dbt-native workflow with documentation as the goal? Start with MetricFlow. Multiple consumers including customer-facing products and AI agents? A standalone semantic layer like Cube or Bonnard.

Self-host free under Apache 2.0, or use Bonnard Cloud for managed infrastructure.

Frequently asked questions

What is a semantic layer in simple terms?

A semantic layer is a single place where you define what business terms like "revenue" or "active user" mean in terms of database columns and calculations. Every tool that needs that number references the same definition instead of writing its own query. One definition, every consumer, same answer.

What is the difference between a semantic layer and a data warehouse?

A data warehouse stores your data. A semantic layer defines what the data means. The warehouse has a column called amount. The semantic layer defines that total_revenue = SUM(amount) WHERE status != 'refunded'. They work together: the warehouse holds the data, the semantic layer holds the business logic.

Is dbt a semantic layer?

dbt is primarily a transformation tool that models raw data into clean tables. dbt's MetricFlow adds metric definition capabilities, which is one part of a semantic layer. But a full semantic layer also includes caching, multi-tenancy, access control, and API serving, which MetricFlow doesn't provide on its own. Most teams use dbt for transformations and a separate semantic layer for metric serving.

What is the difference between a semantic layer and an OBT (One Big Table)?

An OBT flattens everything into a single denormalized table. A semantic layer keeps your data normalized and defines metrics as calculations across tables. OBTs are simpler at first but break down at scale: they're expensive to rebuild, hard to govern, and create massive tables with redundant data. A semantic layer gives you the same query simplicity without the storage and maintenance cost.

What is the difference between a semantic layer and a metrics layer?

The terms are often used interchangeably, but a metrics layer is a subset of a semantic layer. A metrics layer defines measures and dimensions. A semantic layer adds relationships (joins), access control, caching, and API serving on top. dbt's MetricFlow is a metrics layer. Cube and Bonnard are full semantic layers.

Do I need a semantic layer for AI agents?

If AI agents query your data, a semantic layer is the difference between "plausible numbers" and "correct numbers." Without one, agents interpret column names and generate ad-hoc SQL. With one, agents query governed metric definitions and get the same answer every time. See What Is an Agentic Semantic Layer? for the full breakdown.

How does a semantic layer handle performance?

Through pre-aggregation. The semantic layer pre-computes common rollups (daily revenue by status, weekly order count by category) and caches the results. Queries hit the cache instead of scanning raw tables. Hot queries resolve in single-digit milliseconds. The cache rebuilds on a configurable schedule with automatic invalidation when source data changes.

Can I use a semantic layer with Snowflake, BigQuery, and other warehouses?

Yes. Modern semantic layers are warehouse-agnostic. You configure the connection to your warehouse (Snowflake, BigQuery, Databricks, PostgreSQL (including Supabase, Neon, and RDS), Redshift, DuckDB (including MotherDuck)) and the semantic layer generates the appropriate SQL dialect. Swap warehouses without changing your metric definitions.

What is a universal semantic layer?

A universal semantic layer serves every consumer from one set of definitions, regardless of what tool they use. Instead of defining metrics separately in Looker, Power BI, and your application code, you define them once and serve them everywhere. "Universal" means tool-agnostic: the semantic layer isn't embedded in any single BI product.

Ready to ship a customer-ready MCP?

Turn your semantic layer, dbt, or warehouse into a governed, per-customer MCP for your customers' agents.