dbt Project Folder Structure

A dbt project has a specific folder layout. Every folder has a defined purpose. Understanding this structure helps you know exactly where to put each file and where to look when something goes wrong.

Full Project Tree

my_dbt_project/
├── dbt_project.yml          ← Project settings
├── packages.yml             ← External packages
├── profiles.yml             ← (usually in ~/.dbt/, not here)
│
├── models/                  ← Your SQL transformation files
│   ├── staging/
│   │   ├── stg_customers.sql
│   │   ├── stg_orders.sql
│   │   └── schema.yml
│   ├── intermediate/
│   │   └── int_orders_joined.sql
│   └── marts/
│       ├── fct_orders.sql
│       ├── dim_customers.sql
│       └── schema.yml
│
├── tests/                   ← Custom (singular) test SQL files
│   └── assert_order_total_positive.sql
│
├── seeds/                   ← CSV files to load as tables
│   └── country_codes.csv
│
├── snapshots/               ← SCD Type 2 snapshot models
│   └── orders_snapshot.sql
│
├── macros/                  ← Reusable Jinja SQL functions
│   └── cents_to_dollars.sql
│
├── analyses/                ← Ad-hoc SQL not run as models
│   └── revenue_deep_dive.sql
│
├── logs/                    ← dbt run logs (auto-generated)
│   └── dbt.log
│
└── target/                  ← Compiled SQL and run results
    ├── compiled/
    ├── run/
    ├── manifest.json
    └── run_results.json

dbt_project.yml

This is the brain of your project. It tells dbt the project name, which profile to use for database connection, where to find each type of file, and what default settings to apply to models.

name: 'my_dbt_project'
profile: 'my_warehouse'

models:
  my_dbt_project:
    staging:
      +materialized: view
    marts:
      +materialized: table

The indented model config lets you set materializations per folder. All models in staging/ become views. All models in marts/ become tables. Individual models can override these defaults.

The models/ Folder

Every .sql file inside models/ becomes a dbt model — a table or view in your warehouse. You can organize models into subfolders. dbt searches all subfolders recursively.

Recommended Layer Structure

models/
  staging/       ← Clean and rename raw source columns (1:1 with source tables)
  intermediate/  ← Join staging models together, apply business logic
  marts/         ← Final tables used by dashboards and analysts

Think of it like an assembly line:

Raw Source Data
      |
      v
  [staging]    — wash and label the parts
      |
      v
 [intermediate] — assemble the parts
      |
      v
   [marts]     — deliver the finished product

The schema.yml Files

schema.yml files live inside the models/ folder (usually one per subfolder). They contain model descriptions, column descriptions, and test definitions. You can name these files anything ending in .yml, but schema.yml is the convention.

# models/staging/schema.yml
version: 2

models:
  - name: stg_customers
    description: "One row per customer from the raw CRM export"
    columns:
      - name: customer_id
        description: "Primary key"
        tests:
          - not_null
          - unique
      - name: email
        description: "Customer email address"
        tests:
          - not_null

The seeds/ Folder

Seeds are CSV files that dbt loads into your warehouse as tables. Use seeds for small reference datasets that change rarely, such as:

  • Country code to country name mappings
  • Product category labels
  • Region definitions
seeds/country_codes.csv:
code,name
US,United States
IN,India
GB,United Kingdom
DE,Germany

Run dbt seed to load this CSV into a table called country_codes in your schema.

The snapshots/ Folder

Snapshot models track how a record changes over time. If a customer changes their email address, a normal model overwrites the old email. A snapshot keeps both the old and new values with timestamps. Snapshots are covered in detail in a later topic.

The macros/ Folder

Macros are reusable pieces of Jinja SQL. Write a macro once and call it from any model. For example, a macro that converts cents to dollars:

-- macros/cents_to_dollars.sql
{% macro cents_to_dollars(column_name) %}
  ({{ column_name }} / 100.0)::numeric(10,2)
{% endmacro %}

Use it in any model: {{ cents_to_dollars('price_cents') }}

The tests/ Folder

This folder holds singular tests — custom SQL queries where any returned row represents a data quality failure. Generic tests (not_null, unique) go in schema.yml. Complex tests with custom logic go here as .sql files.

The analyses/ Folder

SQL files in analyses/ are compiled by dbt but never executed as models. Use this folder for ad-hoc queries you want to version-control alongside your models without creating warehouse objects.

The target/ Folder

dbt generates this folder automatically every time you run a command. You should add it to your .gitignore file so it does not end up in version control.

# .gitignore
target/
dbt_packages/
logs/

The logs/ Folder

dbt writes a detailed text log of every command you run to logs/dbt.log. When a model fails and the terminal output is not detailed enough, open this file for the full error message and stack trace.

packages.yml

This file lists external dbt packages your project depends on. Packages from the dbt Hub provide pre-built macros and tests you can use without writing them yourself.

packages:
  - package: dbt-labs/dbt_utils
    version: 1.1.1

Run dbt deps after creating or updating this file to download the packages into a dbt_packages/ folder.

Quick Reference: Where Does Each File Go?

File Type                  Folder
-----------------------    ---------
SQL transformation         models/
Data quality YAML config   models/ (schema.yml)
CSV reference data         seeds/
Time-tracking models       snapshots/
Reusable SQL functions     macros/
Ad-hoc queries             analyses/
Custom SQL tests           tests/
Package list               packages.yml (project root)
Project config             dbt_project.yml (project root)

Leave a Comment

Your email address will not be published. Required fields are marked *