The Practice of Data

SQL & Data Modeling

A practical guide to structuring dbt models: staging, intermediate, marts

How to organize a dbt project so new team members can find what they need in seconds, not hours. A layer-by-layer breakdown with naming conventions you can copy today.

If you’ve inherited a dbt project with fifty models dumped into a single folder, you already know the problem this post solves. A clear layering convention is the single highest-leverage thing you can do for a dbt project’s long-term maintainability — and it’s simpler than most teams make it.

The three layers

Staging models do one job: clean up a single source table. Rename columns to your standard naming convention, cast types, and do light deduplication. One staging model per source table, no joins, no business logic. Name them stg_<source>__<entity>, e.g. stg_stripe__charges.

Intermediate models are where joins and business logic live. This is the layer most teams skip, which is why staging and marts models end up bloated with duplicated logic. If two mart models need the same join, that join belongs in an intermediate model. Name them int_<entity>__<verb>, e.g. int_orders__joined_with_refunds.

Mart models are what analysts and BI tools actually query. They’re wide, denormalized, and organized by business area rather than by source system — fct_orders, dim_customers. This is the only layer that should be exposed to non-dbt tools.

Folder structure that matches the layers

models/
  staging/
    stripe/
      stg_stripe__charges.sql
      stg_stripe__customers.sql
      _stripe__sources.yml
  intermediate/
    int_orders__joined_with_refunds.sql
  marts/
    finance/
      fct_orders.sql
      dim_customers.sql

Mirror the folder structure to the source system in staging, and to the business domain in marts. This one decision resolves most of the “where does this model belong” debates that slow teams down in code review.

Testing at each layer

Put not-null and unique tests on primary keys in staging models — this is where you catch source data problems closest to where they originate. Put relationship tests (foreign key integrity) in intermediate or mart models, where the joins actually happen. Testing at the wrong layer means failures point you at the wrong place when something breaks.

What to do this week

Pick your three most-queried mart models. Trace their lineage back to staging. If you find business logic duplicated across two or more marts, that’s your first intermediate model to extract. You don’t need to restructure the whole project at once — layer discipline pays off incrementally, one model at a time.