See all articles

Metabase on a dbt project

Metabase reads the marts that dbt builds in your warehouse, so you point people at those tables and define each key number once as a Metabase metric. Those tables and metrics form a semantic layer that Metabot, the AI assistant in Metabase, uses to answer natural language questions. Metrics, measures and Metabot work on every edition, including the free open-source edition. The Library, verified content and Remote Sync to Git are part of the Pro and Enterprise plans.

How does Metabase read a dbt project?

Metabase connects to your warehouse, not to the dbt project, so the tables and views that dbt builds show up in Metabase after the next schema sync. That works the same whether you run dbt Core or the dbt platform. Metabase has official connectors for BigQuery, Snowflake, Redshift, Databricks, ClickHouse, PostgreSQL and other databases, and by default it syncs the schema during setup and again every hour.

To see new marts right after a dbt run, call the sync endpoint at the end of your CI job. This follows the sync and scan docs and uses an API key that belongs to the Administrators group:

# Run after `dbt build` succeeds.
curl -X POST "$METABASE_URL/api/database/$METABASE_DATABASE_ID/sync_schema" \
  -H "X-API-Key: $METABASE_API_KEY"

You can also choose which schemas Metabase syncs. On PostgreSQL, for example, the Schemas setting accepts “All”, “Only these…” or “All except…”, so you can sync the schemas that hold your dbt models and leave scratch schemas out.

How do I use dbt models and marts in Metabase?

Your dbt marts are already finished tables, so people can query them directly. In Metabase’s Data Studio, open Connected data to manage every synced table. Each table has a Visibility layer that you can match to your dbt layers: Hidden for staging models, Internal for intermediate models and Final for marts. A hidden table doesn’t appear in the query builder and isn’t synced. The visibility layer only shapes what people see, so use data permissions to control access.

On each table’s Fields tab, add descriptions, display names and column types, or copy them from dbt as shown below. On Pro and Enterprise plans, you can then publish your marts to the Library. Metabase’s docs say that tables in the Library “should be finished, polished tables”, and the data picker shows them first.

You don’t need a Metabase model on top of a mart. Metabase’s docs now suggest transforms instead of models, and a transform creates a persistent table in your database, which is what your dbt marts already are. When people need a new joined or reshaped table, add it as a dbt model, so it becomes one more mart.

How do I define dbt metrics once?

Keep the row-level logic in dbt and the aggregation in one Metabase metric. For example, compute net_revenue per order in your fct_orders model, where dbt tests it, and then create a Metabase metric that sums that column. When the revenue rule changes, you change only the dbt model, and the charts that use the metric show the new numbers after the next dbt run.

A metric has a data source, a formula and dimensions, including an optional default dimension that Metabase uses to display it. To create one, search for New metric in the command palette, or open Browse > Metrics and click +. Metabase recommends keeping filters and joins out of the metric definition, so do your joins in dbt and build the metric on the mart. If you edit a metric’s definition, every question that uses it switches to the new definition right away.

People see the metric under Metrics in the Summarize block. The metrics explorer lets them break it out by dimensions, compare it with other metrics and do math with metrics. Metrics live in collections, and on Pro and Enterprise plans you can keep official metrics in Library > Metrics and mark them as verified. For a formula that belongs to one table, such as a satisfaction score, add a measure to that table in Connected data.

To review metric changes the way you review dbt changes, use Remote Sync, which is part of the Pro and Enterprise plans. A development Metabase pushes your Library, including published tables, metrics, measures and the glossary, to a Git repository on GitHub, GitLab or Bitbucket, together with dashboards, questions and transforms. A read-only production Metabase then pulls those changes. Each item is a YAML file in the Metabase Representation Format, whose spec Metabase publishes on GitHub. A metric file looks like this, with example IDs:

name: Net revenue
description: Sum of net_revenue from the dbt model fct_orders.
entity_id: dCP8grf9B9fyYChhbTeG4
collection_id: Rv7QmK2xTb9LpWc4Hn8Za
creator_id: admin@example.com
display: scalar
type: metric
database_id: Warehouse
dataset_query:
  "lib/type": mbql/query
  database: Warehouse
  stages:
    - "lib/type": mbql.stage/mbql
      source-table:
      - Warehouse
      - analytics
      - fct_orders
      aggregation:
      - - sum
        - {}
        - - field
          - base-type: type/Float
          - - Warehouse
            - analytics
            - fct_orders
            - net_revenue
visualization_settings: {}
serdes/meta:
- model: Card

Can Metabase import dbt docs and schema.yml descriptions?

Metabase keeps table and column descriptions in its own metadata, which you edit in Data Studio > Connected data. To copy the descriptions from your dbt schema.yml files, write them through the Metabase API after each dbt run. Remote Sync doesn’t sync table metadata such as descriptions, so run this step in CI next to the sync call above.

The community package dbt-metabase does this in one command. It’s MIT-licensed and isn’t made by Metabase. It reads target/manifest.json, copies model and column descriptions and relationships, and reads metabase.* keys under meta for semantic types and visibility. These examples follow its README:

# models/schema.yml
models:
  - name: fct_orders
    description: One row per order, with revenue net of refunds.
    columns:
      - name: customer_email
        description: Email address used at checkout.
        config:
          meta:
            metabase.semantic_type: type/Email
            metabase.visibility_type: sensitive
pip install dbt-metabase
dbt-metabase models \
    --manifest-path target/manifest.json \
    --metabase-url https://metabase.example.com \
    --metabase-api-key "$METABASE_API_KEY" \
    --metabase-database Warehouse \
    --include-schemas analytics

If you’d rather not add a dependency, this script calls three endpoints from the API reference: GET /api/database/{id}/metadata, PUT /api/table/{id} and PUT /api/field/{id}. It needs the requests package and an API key in the Administrators group. It reads the description fields that dbt writes for each model in target/manifest.json:

# sync_dbt_descriptions.py: copy dbt model and column descriptions into Metabase.
# Needs: pip install requests, and an API key in the Administrators group.
import json
import os

import requests

MB_URL = os.environ["METABASE_URL"]
DB_ID = os.environ["METABASE_DATABASE_ID"]
HEADERS = {"X-API-Key": os.environ["METABASE_API_KEY"]}

with open("target/manifest.json") as f:
    manifest = json.load(f)

metadata = requests.get(
    f"{MB_URL}/api/database/{DB_ID}/metadata",
    params={"include_hidden": "true"},
    headers=HEADERS,
)
metadata.raise_for_status()
tables = {
    ((t["schema"] or "").lower(), t["name"].lower()): t
    for t in metadata.json()["tables"]
}

for node in manifest["nodes"].values():
    if node["resource_type"] != "model":
        continue
    table = tables.get((node["schema"].lower(), node["alias"].lower()))
    if table is None:
        continue
    if node["description"]:
        requests.put(
            f"{MB_URL}/api/table/{table['id']}",
            json={"description": node["description"]},
            headers=HEADERS,
        ).raise_for_status()
    fields = {f["name"].lower(): f["id"] for f in table["fields"]}
    for column in node["columns"].values():
        field_id = fields.get(column["name"].lower())
        if field_id and column.get("description"):
            requests.put(
                f"{MB_URL}/api/field/{field_id}",
                json={"description": column["description"]},
                headers=HEADERS,
            ).raise_for_status()

Can Metabot answer questions from dbt metrics?

Yes, Metabot answers natural language questions from the tables and metrics built on your dbt marts. Metabot creates charts from natural language, generates SQL queries, fixes query errors and analyzes existing charts. The docs say that “Metabot can only search for tables, metrics, questions, models, and dashboards”, so it finds your mart tables and metrics. It can also use segments and measures in the queries it writes.

Two settings in Admin > AI make the answers more consistent. Set a Collection for natural language querying, which limits AI exploration to the collection that holds your metrics. On Pro and Enterprise plans, turn on Verified or curated content, so Metabot only uses content that’s verified, in an official collection or published to the Library. Published tables count only when their visibility layer is Final, which is one more reason to mark your marts as Final. Then define your business terms in the Glossary, because Metabot can look up glossary terms to understand a prompt.

People can ask Metabot inside Metabase, or in Slack, where it answers direct messages and @mentions in channels. AI clients such as Claude Code, Cursor or ChatGPT can read the same content and run queries through the Metabase MCP server, scoped to each person’s permissions. Connecting your own AI provider, such as Anthropic, OpenAI, AWS Bedrock or Microsoft Azure, doesn’t require a paid Metabase plan. On Metabase Cloud, you can use the Metabase AI service instead, with the first 1 million tokens free and then $3.75 per million tokens.

Metabase, Lightdash or the dbt Semantic Layer?

Lightdash reads a connected dbt project and builds its semantic layer from it, and it supports dbt Core and the dbt platform. You define Lightdash metrics in your dbt YAML, under meta on a column or a model, and Lightdash AI agents answer natural language questions from that semantic layer. Lightdash Cloud Pro costs $3,000 a month with unlimited users, and the open-source edition is self-hosted. Lightdash’s pricing page lists AI agents and MCP in Cloud Pro, its docs call AI agents an add-on, and the open-source edition doesn’t include them. Lightdash fits a team that wants every metric written in dbt YAML and reviewed in pull requests next to the dbt models.

The dbt Semantic Layer, powered by MetricFlow, defines semantic models and metrics in YAML inside the dbt project. MetricFlow is open source under the Apache 2.0 license. Without a plan on the dbt platform (formerly dbt Cloud), you can define metrics and query them from the command line. To query them from other tools through the Semantic Layer APIs, you need a dbt Starter plan, at $100 per user a month, or an Enterprise plan. The dbt docs list Snowflake, BigQuery, Databricks, Redshift, Postgres and Trino as supported data platforms, and ClickHouse isn’t on that list.

On a Starter or Enterprise plan, the dbt Semantic Layer also has exports, which write the results of a saved query to a table or view in your warehouse. The dbt docs say that “All BI tools can use exports”, and Metabase reads an export table like any other table. Querying that table doesn’t count toward dbt’s queried metrics. The dbt Semantic Layer fits a team that wants one metric definition shared by spreadsheets, notebooks and several BI tools.

Cube Cloud can import dbt models as cubes on its Premium plan and above. The open-source Cube Core is headless and serves its semantic layer through SQL, REST and GraphQL APIs. It fits a team that wants to build its own interface on top.

Metabase fits a team that wants people across the company to explore the marts and build their own reports, with metrics defined in the same place where they ask questions.

Metabase pricing: which plan do you need?

The free open-source edition, which you host yourself, includes metrics, measures, the Glossary, Metabot with your own AI provider, the MCP server and API keys. Starter costs $100 a month on Metabase Cloud and includes 5 users. Pro costs $575 a month, includes 10 users and adds $12 a month for each extra user. Enterprise starts at $20,000 a year. Pro and Enterprise add the Library, verified content, Remote Sync, serialization, and row and column security.

Next steps

# Metabase on a dbt project

Metabase reads the marts that dbt builds in your warehouse, so you point people at those tables and define each key number once as a Metabase metric. Those tables and metrics form a semantic layer that Metabot, the AI assistant in Metabase, uses to answer natural language questions. Metrics, measures and Metabot work on every edition, including the free open-source edition. The Library, verified content and Remote Sync to Git are part of the Pro and Enterprise plans.

## How does Metabase read a dbt project?

Metabase connects to your warehouse, not to the dbt project, so the tables and views that dbt builds show up in Metabase after the next schema sync. That works the same whether you run dbt Core or the dbt platform. Metabase has official connectors for BigQuery, Snowflake, Redshift, Databricks, ClickHouse, PostgreSQL and other databases, and by default it syncs the schema during setup and again every hour.

To see new marts right after a dbt run, call the sync endpoint at the end of your CI job. This follows the [sync and scan docs](/docs/latest/databases/sync-scan) and uses an [API key](/docs/latest/people-and-groups/api-keys) that belongs to the Administrators group:

```bash
# Run after `dbt build` succeeds.
curl -X POST "$METABASE_URL/api/database/$METABASE_DATABASE_ID/sync_schema" \
  -H "X-API-Key: $METABASE_API_KEY"
```

You can also choose which schemas Metabase syncs. On PostgreSQL, for example, the Schemas setting accepts "All", "Only these…" or "All except…", so you can sync the schemas that hold your dbt models and leave scratch schemas out.

## How do I use dbt models and marts in Metabase?

Your dbt marts are already finished tables, so people can query them directly. In Metabase's Data Studio, open [Connected data](/docs/latest/data-modeling/metadata/managing-tables) to manage every synced table. Each table has a Visibility layer that you can match to your dbt layers: Hidden for staging models, Internal for intermediate models and Final for marts. A hidden table doesn't appear in the query builder and isn't synced. The visibility layer only shapes what people see, so use data permissions to control access.

On each table's Fields tab, add descriptions, display names and column types, or copy them from dbt as shown below. On Pro and Enterprise plans, you can then publish your marts to the [Library](/docs/latest/data-modeling/semantic-layer/library). Metabase's docs say that tables in the Library "should be finished, polished tables", and the data picker shows them first.

You don't need a Metabase [model](/docs/latest/data-modeling/models/models) on top of a mart. Metabase's docs now suggest transforms instead of models, and a transform creates a persistent table in your database, which is what your dbt marts already are. When people need a new joined or reshaped table, add it as a dbt model, so it becomes one more mart.

## How do I define dbt metrics once?

Keep the row-level logic in dbt and the aggregation in one Metabase metric. For example, compute `net_revenue` per order in your `fct_orders` model, where dbt tests it, and then create a Metabase [metric](/docs/latest/data-modeling/semantic-layer/metrics) that sums that column. When the revenue rule changes, you change only the dbt model, and the charts that use the metric show the new numbers after the next dbt run.

A metric has a data source, a formula and dimensions, including an optional default dimension that Metabase uses to display it. To create one, search for New metric in the command palette, or open Browse > Metrics and click +. Metabase recommends keeping filters and joins out of the metric definition, so do your joins in dbt and build the metric on the mart. If you edit a metric's definition, every question that uses it switches to the new definition right away.

People see the metric under Metrics in the Summarize block. The [metrics explorer](/docs/latest/questions/metrics-explorer) lets them break it out by dimensions, compare it with other metrics and do math with metrics. Metrics live in collections, and on Pro and Enterprise plans you can keep official metrics in Library > Metrics and mark them as verified. For a formula that belongs to one table, such as a satisfaction score, add a [measure](/docs/latest/data-modeling/semantic-layer/measures) to that table in Connected data.

To review metric changes the way you review dbt changes, use [Remote Sync](/docs/latest/installation-and-operation/remote-sync), which is part of the Pro and Enterprise plans. A development Metabase pushes your Library, including published tables, metrics, measures and the glossary, to a Git repository on GitHub, GitLab or Bitbucket, together with dashboards, questions and transforms. A read-only production Metabase then pulls those changes. Each item is a YAML file in the Metabase Representation Format, whose spec Metabase publishes [on GitHub](https://github.com/metabase/representations). A metric file looks like this, with example IDs:

```yaml
name: Net revenue
description: Sum of net_revenue from the dbt model fct_orders.
entity_id: dCP8grf9B9fyYChhbTeG4
collection_id: Rv7QmK2xTb9LpWc4Hn8Za
creator_id: admin@example.com
display: scalar
type: metric
database_id: Warehouse
dataset_query:
  "lib/type": mbql/query
  database: Warehouse
  stages:
    - "lib/type": mbql.stage/mbql
      source-table:
      - Warehouse
      - analytics
      - fct_orders
      aggregation:
      - - sum
        - {}
        - - field
          - base-type: type/Float
          - - Warehouse
            - analytics
            - fct_orders
            - net_revenue
visualization_settings: {}
serdes/meta:
- model: Card
```

## Can Metabase import dbt docs and schema.yml descriptions?

Metabase keeps table and column descriptions in its own metadata, which you edit in Data Studio > Connected data. To copy the descriptions from your dbt `schema.yml` files, write them through the Metabase API after each dbt run. Remote Sync doesn't sync table metadata such as descriptions, so run this step in CI next to the sync call above.

The community package [dbt-metabase](https://github.com/gouline/dbt-metabase) does this in one command. It's MIT-licensed and isn't made by Metabase. It reads `target/manifest.json`, copies model and column descriptions and relationships, and reads `metabase.*` keys under `meta` for semantic types and visibility. These examples follow its README:

```yaml
# models/schema.yml
models:
  - name: fct_orders
    description: One row per order, with revenue net of refunds.
    columns:
      - name: customer_email
        description: Email address used at checkout.
        config:
          meta:
            metabase.semantic_type: type/Email
            metabase.visibility_type: sensitive
```

```bash
pip install dbt-metabase
dbt-metabase models \
    --manifest-path target/manifest.json \
    --metabase-url https://metabase.example.com \
    --metabase-api-key "$METABASE_API_KEY" \
    --metabase-database Warehouse \
    --include-schemas analytics
```

If you'd rather not add a dependency, this script calls three endpoints from the [API reference](/docs/latest/api): `GET /api/database/{id}/metadata`, `PUT /api/table/{id}` and `PUT /api/field/{id}`. It needs the `requests` package and an API key in the Administrators group. It reads the description fields that dbt writes for each model in `target/manifest.json`:

```python
# sync_dbt_descriptions.py: copy dbt model and column descriptions into Metabase.
# Needs: pip install requests, and an API key in the Administrators group.
import json
import os

import requests

MB_URL = os.environ["METABASE_URL"]
DB_ID = os.environ["METABASE_DATABASE_ID"]
HEADERS = {"X-API-Key": os.environ["METABASE_API_KEY"]}

with open("target/manifest.json") as f:
    manifest = json.load(f)

metadata = requests.get(
    f"{MB_URL}/api/database/{DB_ID}/metadata",
    params={"include_hidden": "true"},
    headers=HEADERS,
)
metadata.raise_for_status()
tables = {
    ((t["schema"] or "").lower(), t["name"].lower()): t
    for t in metadata.json()["tables"]
}

for node in manifest["nodes"].values():
    if node["resource_type"] != "model":
        continue
    table = tables.get((node["schema"].lower(), node["alias"].lower()))
    if table is None:
        continue
    if node["description"]:
        requests.put(
            f"{MB_URL}/api/table/{table['id']}",
            json={"description": node["description"]},
            headers=HEADERS,
        ).raise_for_status()
    fields = {f["name"].lower(): f["id"] for f in table["fields"]}
    for column in node["columns"].values():
        field_id = fields.get(column["name"].lower())
        if field_id and column.get("description"):
            requests.put(
                f"{MB_URL}/api/field/{field_id}",
                json={"description": column["description"]},
                headers=HEADERS,
            ).raise_for_status()
```

## Can Metabot answer questions from dbt metrics?

Yes, Metabot answers natural language questions from the tables and metrics built on your dbt marts. [Metabot](/docs/latest/ai/metabot) creates charts from natural language, generates SQL queries, fixes query errors and analyzes existing charts. The docs say that "Metabot can only search for tables, metrics, questions, models, and dashboards", so it finds your mart tables and metrics. It can also use segments and measures in the queries it writes.

Two settings in [Admin > AI](/docs/latest/ai/settings) make the answers more consistent. Set a Collection for natural language querying, which limits AI exploration to the collection that holds your metrics. On Pro and Enterprise plans, turn on Verified or curated content, so Metabot only uses content that's verified, in an official collection or published to the Library. Published tables count only when their visibility layer is Final, which is one more reason to mark your marts as Final. Then define your business terms in the [Glossary](/docs/latest/data-modeling/semantic-layer/glossary), because Metabot can look up glossary terms to understand a prompt.

People can ask Metabot inside Metabase, or in Slack, where it answers direct messages and @mentions in channels. AI clients such as Claude Code, Cursor or ChatGPT can read the same content and run queries through the [Metabase MCP server](/docs/latest/ai/mcp), scoped to each person's permissions. Connecting your own AI provider, such as Anthropic, OpenAI, AWS Bedrock or Microsoft Azure, doesn't require a paid Metabase plan. On Metabase Cloud, you can use the Metabase AI service instead, with the first 1 million tokens free and then $3.75 per million tokens.

## Metabase, Lightdash or the dbt Semantic Layer?

Lightdash reads a connected dbt project and builds its semantic layer from it, and it supports dbt Core and the dbt platform. You define Lightdash metrics in your dbt YAML, under `meta` on a column or a model, and Lightdash AI agents answer natural language questions from that semantic layer. Lightdash Cloud Pro costs $3,000 a month with unlimited users, and the open-source edition is self-hosted. Lightdash's pricing page lists AI agents and MCP in Cloud Pro, its docs call AI agents an add-on, and the open-source edition doesn't include them. Lightdash fits a team that wants every metric written in dbt YAML and reviewed in pull requests next to the dbt models.

The dbt Semantic Layer, powered by MetricFlow, defines semantic models and metrics in YAML inside the dbt project. MetricFlow is open source under the Apache 2.0 license. Without a plan on the dbt platform (formerly dbt Cloud), you can define metrics and query them from the command line. To query them from other tools through the Semantic Layer APIs, you need a dbt Starter plan, at $100 per user a month, or an Enterprise plan. The dbt docs list Snowflake, BigQuery, Databricks, Redshift, Postgres and Trino as supported data platforms, and ClickHouse isn't on that list.

On a Starter or Enterprise plan, the dbt Semantic Layer also has exports, which write the results of a saved query to a table or view in your warehouse. The dbt docs say that "All BI tools can use exports", and Metabase reads an export table like any other table. Querying that table doesn't count toward dbt's queried metrics. The dbt Semantic Layer fits a team that wants one metric definition shared by spreadsheets, notebooks and several BI tools.

Cube Cloud can import dbt models as cubes on its Premium plan and above. The open-source Cube Core is headless and serves its semantic layer through SQL, REST and GraphQL APIs. It fits a team that wants to build its own interface on top.

Metabase fits a team that wants people across the company to explore the marts and build their own reports, with metrics defined in the same place where they ask questions.

## Metabase pricing: which plan do you need?

The free open-source edition, which you host yourself, includes metrics, measures, the Glossary, Metabot with your own AI provider, the MCP server and API keys. Starter costs $100 a month on Metabase Cloud and includes 5 users. Pro costs $575 a month, includes 10 users and adds $12 a month for each extra user. Enterprise starts at $20,000 a year. Pro and Enterprise add the Library, verified content, Remote Sync, serialization, and row and column security.

## Next steps

- [Connected data](/docs/latest/data-modeling/metadata/managing-tables)
- [Metrics](/docs/latest/data-modeling/semantic-layer/metrics)
- [Library](/docs/latest/data-modeling/semantic-layer/library)
- [Metabot](/docs/latest/ai/metabot)
- [Models](/docs/latest/data-modeling/models/models)
- [Remote Sync](/docs/latest/installation-and-operation/remote-sync)
- [Sync and scan](/docs/latest/databases/sync-scan)
- [API reference](/docs/latest/api)
- [Pricing](/pricing)