---
title: "dbt staging models: how to set up the staging layer | Metacenta"
description: "A dbt staging model tidies one source table. How to name and organise staging models, what belongs in them, how to materialise them, and where joins go."
url: "https://metacenta.com/rules/staging-depends-on-staging"
updated: "2026-10-14"
tokens: 1204
---

Modelling correctness · rule staging-depends-on-staging

# dbt staging models: how to set up the staging layer

A dbt staging model tidies one source table and passes it on. The staging layer is the only place that calls `source()`, so everything downstream builds on clean, consistently named data.

A Metacenta review checks this under the rule **Staging models read sources, not each other**. Everything below applies whether or not you ever commission one.

## How to organise and name staging models

dbt Labs' guide gives each source system its own folder, and names each file `stg_[source]__[entity]s.sql`. The double underscore separates the source from the entity. Don't group these folders by loader or business area.

```text
models/staging/
├── shop/
│   ├── _shop__sources.yml
│   ├── stg_shop__customers.sql
│   └── stg_shop__orders.sql
└── stripe/
    ├── _stripe__sources.yml
    └── stg_stripe__payments.sql
```

Each source table gets one staging model.

## What belongs in a staging model

- Renaming columns
- Casting types
- Basic calculations, such as cents to pounds
- Categorising values with conditional logic

Leave out joins and aggregations. A join belongs in an intermediate model, and an aggregation changes the grain and drops detail you may need later.

```sql
select
    id as order_id,
    customer as customer_id,
    total_cents / 100.0 as order_total,
    created_at as ordered_at
from {{ source('shop', 'orders') }}
```

## How to materialise staging models

dbt Labs recommends views. Downstream models then always read current data, and you don't store tables nobody queries directly. Set it once for the folder.

```yaml
# dbt_project.yml
models:
  my_project:
    staging:
      +materialized: view
```

## When staging needs a join

Sometimes a clean table needs a join, such as applying a separate table of deletions. dbt Labs' answer is a `base/` folder under the source's staging folder: base models read the raw tables, and a staging model joins them.

This rule treats a `base/` folder under `staging/` as staging, so it flags that join. Record it in the model's description as a deliberate choice. See [dbt staging models that read marts](https://metacenta.com/rules/staging-depends-on-downstream) for the related rule.

## What this rule checks

This rule flags staging models that read another staging model. We place a model in a layer from its path: a `staging`, `intermediate` or marts folder. If no model's path places it in a layer, the rule declines.

## Why it matters

Staging stops being the one place a raw table is tidied. Reading a staged model no longer tells you what it contains; you have to follow the chain back.

## How to fix it

**Point each cleaning step at raw data, not at another cleaning step.** Move the joining or reshaping logic into an intermediate model, and leave each staging model reading only its source.

Before:

```sql
-- models/staging/shop/stg_shop__orders_enriched.sql
select o.*, c.region
from {{ ref('stg_shop__orders') }} as o
join {{ ref('stg_shop__customers') }} as c using (customer_id)
```

After:

```sql
-- models/intermediate/int_orders_with_region.sql
select o.*, c.region
from {{ ref('stg_shop__orders') }} as o
join {{ ref('stg_shop__customers') }} as c using (customer_id)
```

## When it is fine to leave

Some teams deliberately build a second staging model over the first, such as deduplicating after a rename. That is a defensible style, which is why we rate this low severity at medium confidence. Say so in the model's description.

## What we need to check it

`manifest.json` alone. Layers come from folder names; marts folders are `marts`, `mart`, `presentation` or `reporting`. A project that uses no such folders is not judged here.

## Published rules it corresponds to

**[dbt_project_evaluator](https://dbt-labs.github.io/dbt-project-evaluator/)**, rule `fct_staging_dependent_on_staging`. Ours checks the same condition and threshold.

This means our check corresponds to their rule. It does not mean the publisher reviewed or endorses it.

[Ask about a review](https://metacenta.com/enquire?src=rule-staging-depends-on-staging&via=md)

## Related pages

- [dbt incremental models: how unique_key works](https://metacenta.com/rules/incremental-without-unique-key)
- [dbt public models with no enforced contract](https://metacenta.com/rules/public-model-without-contract)
- [dbt sources declared twice for one table](https://metacenta.com/rules/duplicate-source)
- [dbt staging models that read marts](https://metacenta.com/rules/staging-depends-on-downstream)
- [A sample review, as a client reads it](https://metacenta.com/sample)
- [All published rules](https://metacenta.com/rules)
- [How a review is assessed](https://metacenta.com/standard)
