Home / Guides

Checklist

dbt project health checklist: 30 checks

Use this to score an existing dbt project in under an hour. Give one point for each check that's fully true today. It's the same ground a Data Foundation Audit covers, condensed into a self-assessment.

Where the 30 checks apply in your stackThe stack runs left to right: sources, staging, intermediate, marts, semantic layer, BI and AI. Sources checks #1–5 cover sources and staging. Modelling #6–11 covers intermediate and marts. Documentation and semantics #18–22 covers the semantic layer and BI. Testing #12–17, highlighted, spans staging to marts. Workflow and CI #23–26 spans the whole stack through the Git repo, pull requests and CI. Operations, cost and access #27–30 spans the warehouse and orchestration from staging to BI.Data flows left to rightSourcesstagingintermediatemartsSemanticBI + AISources#1–5Modelling#6–11Documentation & semantics#18–22Testing#12–17Workflow & CI · Git repo, pull requests, CI#23–26Operations, cost & access · warehouse, orchestration#27–30Where the 30 checks apply in your stackThe stack runs left to right: sources, staging, intermediate, marts, semantic layer, BI and AI. Sources checks #1–5 cover sources and staging. Modelling #6–11 covers intermediate and marts. Documentation and semantics #18–22 covers the semantic layer and BI. Testing #12–17, highlighted, spans staging to marts. Workflow and CI #23–26 spans the whole stack through the Git repo, pull requests and CI. Operations, cost and access #27–30 spans the warehouse and orchestration from staging to BI.Squares = where each area applies,sources → staging → int → marts → sem → BISources#1–5Modelling#6–11Testing#12–17Docs & semantics#18–22Workflow & CI#23–26Ops, cost & access#27–30
Figure 1. Most checks live in the dbt layers, but CI and operations cut across the whole stack.

Prefer a spreadsheet? Get the free dbt Project Health Scorecard (XLSX with automatic scoring, plus a PDF).

dbt project layers and what belongs in eachFive layers, left to right. Sources (sources.yml): declared once, each with an owner and freshness. Staging (stg_): one model per source table that only renames and casts, no joins. Intermediate (int_): reusable joins and business logic in small models. Marts (fct_ and dim_): a stated grain, tested keys, documented. Semantic layer (metrics): one definition per metric, used by BI and AI. Across every layer: tests in YAML, generated docs, changes via pull request and CI, separate dev and prod. The highlighted key path is marts to the semantic layer.Sourcessources.ymldeclared oncehas an ownerfreshness setStagingstg_1:1 per tablerename · castno joinsIntermediateint_reusable joinsbusiness logicsmall modelsMartsfct_ + dim_stated grainkeys testeddocumentedSemanticmetricsone per metricused by BI + AImapped to BIAcross every layer: tests in YAML · generated docs · changes via PR + CI · separate dev and proddbt project layers and what belongs in eachFive layers, left to right. Sources (sources.yml): declared once, each with an owner and freshness. Staging (stg_): one model per source table that only renames and casts, no joins. Intermediate (int_): reusable joins and business logic in small models. Marts (fct_ and dim_): a stated grain, tested keys, documented. Semantic layer (metrics): one definition per metric, used by BI and AI. Across every layer: tests in YAML, generated docs, changes via pull request and CI, separate dev and prod. The highlighted key path is marts to the semantic layer.Sourcessources.yml · declared, owned, freshStagingstg_ · 1:1, rename + cast, no joinsIntermediateint_ · joins, logic, small modelsMartsfct_ + dim_ · grain, keys tested, docsSemanticmetrics · one per metric, BI + AIEvery layer: tests, docs, PR + CI
Figure 2. Each kind of logic lives in exactly one layer, so a change is made once and flows right.

Sources

  1. Every raw table is declared as a dbt source, not referenced by hard-coded name
  2. Source freshness is configured for every source that feeds a business metric
  3. Each source has an owner and a description
  4. Staging models are one-to-one with source tables and only rename, cast and clean
  5. No model selects directly from raw tables outside staging

Modelling

  1. A clear layer structure (e.g. staging → intermediate → marts) is followed consistently
  2. Naming conventions (stg_, int_, fct_, dim_) are documented and enforced
  3. Each mart has a stated grain ("one row per …") in its description
  4. No circular or tangled dependencies; the DAG reads left to right
  5. Very large "god models" are split into testable intermediate steps
  6. Business logic lives in one place, not copied across models

Testing

  1. Primary keys have unique and not_null tests
  2. Foreign keys have relationships tests
  3. Accepted-values tests guard status and type columns
  4. Key metrics have at least one sanity test (e.g. non-negative revenue)
  5. Test failures alert someone, not just a log file
  6. Tests run on every pull request, not only in production

Documentation & semantics

  1. Every mart and its key columns are described
  2. dbt docs are generated and reachable by the team
  3. Core metrics (revenue, MRR, active users…) have one written definition
  4. BI tools use those shared definitions instead of re-implementing them
  5. Exposures map which dashboards depend on which models

Workflow & CI

  1. All changes go through pull requests with review
  2. CI builds and tests modified models (slim CI / state-based selection)
  3. Separate dev and prod targets; nobody develops against prod
  4. dbt and package versions are pinned

Operations, cost & access

  1. Orchestration runs are monitored and failures are visible
  2. Expensive models are identified and materialised appropriately (incremental where it pays)
  3. Warehouse roles follow least privilege; service accounts are separate from people
  4. A runbook explains how to deploy, backfill and recover
dbt model lineage: from raw sources to fct_revenue and dim_customersA left-to-right DAG. Staging models stg_stripe__charges, stg_hubspot__deals, stg_hubspot__companies and stg_app__users each read one source table and have tested keys. stg_stripe__charges feeds int_revenue__monthly. Deals, companies and users fan in to int_customers__joined, which also feeds int_revenue__monthly. int_revenue__monthly builds fct_revenue (grain: account by month) and int_customers__joined builds dim_customers (grain: one per customer); a relationships test links them. Only the semantic layer reads the marts: fct_revenue and dim_customers feed the mrr metric, and the revenue dashboard reads metric: mrr, never the marts directly. The highlighted key path is charges to int_revenue__monthly to fct_revenue to mrr to the revenue dashboard.Staging · keys testedIntermediateMartsExposuresstg_stripe__chargesfrom stripe.chargesstg_hubspot__dealsfrom hubspot.dealsstg_hubspot__companiesfrom hubspot.companiesstg_app__usersfrom app.usersint_revenue__monthlycharges × customersint_customers__joineddeals, companies, usersfct_revenuegrain: account × monthdim_customersgrain: one per customermetric: mrrsemantic layerRevenuedashboardrelationshipsref() / source()testKey pathdbt model lineage: from raw sources to fct_revenue and dim_customersA left-to-right DAG. Staging models stg_stripe__charges, stg_hubspot__deals, stg_hubspot__companies and stg_app__users each read one source table and have tested keys. stg_stripe__charges feeds int_revenue__monthly. Deals, companies and users fan in to int_customers__joined, which also feeds int_revenue__monthly. int_revenue__monthly builds fct_revenue (grain: account by month) and int_customers__joined builds dim_customers (grain: one per customer); a relationships test links them. Only the semantic layer reads the marts: fct_revenue and dim_customers feed the mrr metric, and the revenue dashboard reads metric: mrr, never the marts directly. The highlighted key path is charges to int_revenue__monthly to fct_revenue to mrr to the revenue dashboard.4 staging modelsstripe, hubspot ×2, app · keys testedIntermediateint_customers__joineddeals, companies, usersint_revenue__monthlycharges × customersMartsfct_revenueaccount×monthdim_customersper customermetric: mrrsemantic layer: reads the martsRevenue dashboardreads metric: mrr only
Figure 3. Business logic lives once, in intermediate and marts; everything downstream refs it rather than copying it.

How to read your score

Tests and CI: from pull request to productionBefore merge: a developer opens a pull request on a branch with a dev target. Slim CI builds only changed models and their children in a temporary schema, then tests run (unique, not_null, relationships, accepted_values). If tests fail, the change goes back to the pull request (dashed loop). If they pass, a teammate reviews it. After merge to main: the orchestrator runs the scheduled production dbt build, then dbt source freshness; any failure alerts a person in Slack (dashed). The highlighted key path is review to merge to the production build.Pull request · before mergeProduction · after mergeOpen PRdev targetSlim CIchanged onlyTestskeys testedReviewa teammateMerge to maintriggers productionScheduled dbt buildDagster or Airflow · prodSource freshnessdbt source freshnessAlerts → Slacka person, not a log filetemp schema per PRfail → back to PROn every PRchanged models + children only: fast, cheapOn schedulefull prod build + freshness on key sourcesOn failurea person is alerted, not just a log fileFlowFeedback / alertKey pathTests and CI: from pull request to productionBefore merge: a developer opens a pull request on a branch with a dev target. Slim CI builds only changed models and their children in a temporary schema, then tests run (unique, not_null, relationships, accepted_values). If tests fail, the change goes back to the pull request (dashed loop). If they pass, a teammate reviews it. After merge to main: the orchestrator runs the scheduled production dbt build, then dbt source freshness; any failure alerts a person in Slack (dashed). The highlighted key path is review to merge to the production build.Pull request · dashed = fail loopOpen PRbranch · dev targetSlim CIchanged models, temp schemaTestskeys + relationshipsReviewa teammate approvesAfter mergeMerge to maintriggers productionScheduled dbt buildDagster or AirflowSource freshnessdbt source freshnessAlerts → Slackon any failure
Figure 4. Breaks are caught on the pull request, before merge; production failures page a person.

Give yourself 1 point for each check you fully pass (30 checks, max 30).

Using the scorecard spreadsheet? It also lets you mark a check Partly, worth 0.5 points, so half-points are possible: anything from 25 up reads as 25–30, from 15 up to 24.5 as 15–24, and below 15 as under 15. These bands are a rule of thumb, not a benchmark.

Want a second pair of eyes?

The Data Foundation Audit reviews your warehouse, dbt, orchestration and BI in 7–10 days, and ends in a ranked 90-day roadmap. Planning a hire instead? Read first data hire vs a consultancy.