Skip to main content
If you define metrics in dbt’s semantic layer (MetricFlow), the @growthbook/dbt CLI turns them into GrowthBook Fact Tables and Fact Metrics and keeps them in sync. It reads the artifacts dbt writes after dbt parse, so it needs no warehouse connection and no dbt runtime of its own. This builds on the same bulk import API as the GitHub metrics guide. Fact Tables and Fact Metrics it creates are marked as managed by dbt, so they are read-only in the GrowthBook UI: change them in dbt and sync again. Self-hosted GrowthBook needs a version that supports dbt-managed resources.

How Definitions Map

Both the legacy YAML spec (semantic_models with measures) and the dbt v1.12 spec (semantic_model nested under a model, measures replaced by simple metrics) are supported, because the CLI reads manifest.json rather than your YAML. dbt aggregates a metric over a time grain, while GrowthBook aggregates it per experiment unit and then compares variations. A synced metric therefore will not reproduce the values on your dbt-powered dashboards: a sum becomes the average per-user total, and an average becomes total value over total rows across all exposed users.

Not Supported

These are skipped with a message explaining why. Add --strict to fail the run instead.
  • min aggregation
  • non_additive_dimension
  • Cumulative metrics with grain_to_date (period-to-date resets)
  • Conversion metrics with constant_properties or calculation: conversions
  • Derived metrics other than a / b
  • Filters that reach a dimension two or more joins away, or that use TimeDimension() or Metric()
  • Ratio inputs that are themselves averages or percentiles
  • Percentiles below 0.001 or above 0.999
  • A joined dimension whose <entity>__<dimension> column name is already a column on the base model
  • Two metrics that share a column name but disagree on what it holds (for example, a physical net_revenue_vc column and the virtual column for a net_revenue measure)
Quantile and Funnel metrics require a GrowthBook plan that includes them.

Setup

1. Find Your Data Source Id and Create an API Key

Go to Metrics and Data → Data Sources, open the Data Source that your dbt project writes to, and copy the id from the end of the URL (it starts with ds_). Then go to Settings → API Keys and create a Secret Key with the Admin role.

2. Run the Sync

After dbt parse (or any dbt command that writes target/manifest.json):
The dry run translates your project, validates the result against GrowthBook without writing anything, and prints everything it skipped. Drop --dry-run to apply. If your API key requires a person to confirm writes, the CLI prints the confirmation link and waits until they decide. Self-hosting? Set GROWTHBOOK_API_HOST or pass --api-host https://api.your-host.example.

3. Automate It

Run the same command wherever you already run dbt. For example, as a GitHub Actions step after dbt parse:

Entities

GrowthBook needs to know which columns identify an experiment unit (user_id, anonymous_id, and so on). The CLI fetches your Data Source’s identifier types and uses every dbt entity whose name matches one. Use --entities to map entities with different names:
Entities that don’t map to an identifier type are ignored. A semantic model with no mapped entity is skipped along with its metrics, since GrowthBook could not join it to experiment exposures.

Choosing What to Sync

Two ways, which can be combined:
  • In dbt, set config.meta.growthbook.enabled: false on a metric or semantic model. Disabling a model also drops every metric that reads from it.
  • On the command line, pass --select or --exclude with comma-separated metric or model names (* wildcards allowed) or tag:<tag> selectors.
By default, metrics removed from dbt stay in GrowthBook. Add --prune to archive dbt-managed Fact Tables and Fact Metrics on the Data Source that are no longer produced by the sync. Prune never archives resources created any other way, including through the API. They are archived rather than deleted so existing experiment results keep working, and a later --prune run unarchives them if they reappear in dbt. Combine --prune with --dry-run to list what would be archived or unarchived without changing anything. Because a selected subset is not the full picture, --prune cannot be combined with --select or --exclude, and it refuses to run when a Fact Table was skipped (for example because no entity maps to an identifier type), since that would archive metrics still defined in dbt; metrics disabled with meta.growthbook.enabled: false are still archived.

Overrides

dbt has no vocabulary for conversion windows, capping, inverse metrics, priors or projects. Set those under config.meta.growthbook on a metric or semantic model, using the field names from the Fact Metric and Fact Table API docs. They are merged last and win over the translated values. Fields the sync derives from dbt (name, description, tags, lookback windows) are always sent, so removing them in dbt removes them in GrowthBook. Fields that only come from meta.growthbook are left untouched when you delete the key, because the API keeps omitted settings. To clear one, set it explicitly, for example cappingSettings: { type: none }.

Models Without a Semantic Model

A plain dbt model can also become a Fact Table by describing it in GrowthBook’s own terms under meta.growthbook. userIdTypes marks it as a Fact Table; metrics entries use the Fact Metric API fields plus an id, and their numerators default to this table. The timestamp, identifier and metric columns are declared for you (metric columns as numbers, or strings for count distinct); the rest are detected from the SQL.

Reference

--out writes the exact bulk import payload, which you can inspect or feed to the script from the GitHub metrics guide. Fact Table ids are the semantic model names (with __<entity> appended for joined tables and __<dimension> for measures that use a different time dimension). Fact Metric ids are the dbt metric names with a fact__ prefix (revenue becomes fact__revenue). The sync upserts by id, so renaming a metric in dbt creates a new one in GrowthBook; use --prune to archive the old one. It also means an existing Fact Table or Fact Metric with the same id, however it was created, is overwritten by the sync and becomes dbt-managed. A plain model whose name matches a generated Fact Table id is skipped. A plain-model metric whose id matches another metric’s, with or without the fact__ prefix, is skipped.