Every dbt project has roughly the same structure. The conventions are defaults — you can override them — but the dbt community has settled on a layout that's the same across every shop you'll work in.
The standard layout
my_dbt_project/
├── dbt_project.yml # project config
├── profiles.yml # warehouse connection (usually in ~/.dbt/, not committed)
├── packages.yml # external dbt packages
├── models/
│ ├── staging/
│ │ ├── shopify/
│ │ │ ├── stg_shopify__orders.sql
│ │ │ ├── stg_shopify__customers.sql
│ │ │ └── _shopify__sources.yml
│ │ └── stripe/
│ │ └── ...
│ ├── intermediate/
│ │ └── int_orders_with_customers.sql
│ └── marts/
│ ├── core/
│ │ ├── dim_customers.sql
│ │ └── fct_orders.sql
│ └── marketing/
│ └── fct_marketing_touches.sql
├── tests/
│ └── custom_test_no_negative_revenue.sql
├── macros/
│ ├── generate_schema_name.sql
│ └── cents_to_dollars.sql
├── seeds/
│ └── currency_codes.csv
├── snapshots/
│ └── customers_snapshot.sql
├── analyses/
│ └── one_off_analysis.sql
└── target/ # gitignored — compiled output, fresh per run
└── compiled/
What each folder is for
dbt_project.yml
Project-level config. Names the project, points at folder locations, sets default materializations per folder.
name: my_dbt_project
version: 1.0.0
profile: my_warehouse_profile
models:
my_dbt_project:
staging:
+materialized: view
marts:
+materialized: table
This says "everything under models/staging is materialized as a view; everything under models/marts as a table." You can override per model.
profiles.yml
Lives in ~/.dbt/profiles.yml (NOT committed — has credentials). Tells dbt how to connect to your warehouse.
my_warehouse_profile:
target: dev
outputs:
dev:
type: snowflake
account: xy12345.us-east-1
user: anuj
password: "{{ env_var('SNOWFLAKE_PASSWORD') }}"
database: analytics_dev
schema: dbt_anuj
prod:
type: snowflake
account: xy12345.us-east-1
user: dbt_prod_user
password: "{{ env_var('SNOWFLAKE_PASSWORD') }}"
database: analytics
schema: dbt
Same project, two output destinations: dev writes to analytics_dev.dbt_anuj, prod writes to analytics.dbt.
models/
Where the SQL lives. Standard sub-structure:
staging/<source>/— one model per raw source table. Light cleaning.intermediate/— joins, reusable transformations. Often ephemeral or views.marts/<domain>/— facts, dimensions, business-facing models. Usually tables.
Each model is one .sql file with one SELECT statement.
tests/
Custom SQL tests. Each file is a SELECT that returns rows when the test FAILS. dbt expects zero rows = pass.
-- tests/no_negative_revenue.sql
SELECT * FROM {{ ref('fct_orders') }} WHERE revenue < 0;
If fct_orders has any row with negative revenue, the test returns those rows and dbt marks it failed.
macros/
Reusable Jinja/SQL macros. Like Python functions for SQL.
-- macros/cents_to_dollars.sql
{% macro cents_to_dollars(cents_column) %}
({{ cents_column }}::numeric / 100.0)::numeric(10,2)
{% endmacro %}
Used in a model: SELECT {{ cents_to_dollars('amount_cents') }} AS amount FROM ....
seeds/
Small CSV files committed to the repo, loaded into the warehouse as tables. For static reference data (currency codes, region mappings, holidays). dbt seed loads them.
snapshots/
dbt's built-in Type 2 SCD tracker. Each snapshot captures changes to a source table over time.
analyses/
Ad-hoc SQL queries you want to keep in the repo but NOT materialize. Compiled by dbt (so ref() works) but not run as part of dbt run.
target/
dbt's working directory. Compiled SQL output, run artifacts, lineage manifests. Gitignored — generated fresh each run.
The naming conventions
These are conventions, not rules — but every team uses something close:
| Layer | Naming |
|---|---|
| Staging | stg_<source>__<table> (e.g., stg_shopify__orders) |
| Intermediate | int_<description> (e.g., int_orders_with_customers) |
| Dimensions | dim_<entity> (e.g., dim_customers) |
| Facts | fct_<event> (e.g., fct_orders) |
The double-underscore in staging names is a dbt-community convention to visually separate source from table.
What you don't see (and shouldn't add)
- Long folders nested 6 levels deep — dbt projects are flat-ish. 3 levels max under
models/. - Custom build scripts —
dbt run,dbt test,dbt buildcover 95% of needs. - SQL files that aren't models — those go under
analyses/if you want them committed.
Takeaway
Memorize the standard layout. Every dbt project you'll see is a variation of it. Once you can read one project, you can read them all.