Going further 05~7 min
Project configuration
Why does a staging model become a view in the right schema with the right grants, when your file contains nothing but a SELECT? Because dbt_project.yml decided it already.
One file, all the defaults
dbt_project.yml at the repo root configures every model by folder path. An abridged view of ours:
name: 'wnl_analytics'
profile: 'wnl_analytics'
models:
wnl_analytics:
+materialized: table # project default
+persist_docs:
columns: true # YAML descriptions -> Snowflake comments
+post-hook: ["{{ grant_ownership_with_copy() }}"]
raw:
+materialized: view
+database: "STAGING"
+schema: "DBT_RAW"
staging:
+materialized: view # staging overrides the default
+database: "STAGING"
+tags: ["staging", "daily"]
reporting:
+database: "REPORTING"
+tags: ["reporting"]
commissioning:
+schema: "COMMISSIONING_REPORTING"
published:
+tags: ["published_reporting"]
direct_care:
+database: "PUBLISHED_REPORTING__DIRECT_CARE"
secondary_use:
+database: "PUBLISHED_REPORTING__SECONDARY_USE"Read it top-down: settings cascade from project → folder → subfolder, with deeper levels winning. The + prefix marks a config property (as opposed to a folder name).
Raw is the exception with one fixed schema: STAGING.DBT_RAW. Staging models use source or domain schemas within the STAGING database, including CSDS, OLIDS andREFERENCE. The project's schema configuration derives or assigns the appropriate schema rather than putting every staging model into one shared schema.
The hierarchy
When the same setting is defined in more than one place, the most specific wins:
| Level | Example | Wins over |
|---|---|---|
config() in the model file | {{ config(materialized='view') }} | everything below |
| Folder in dbt_project.yml | staging: +materialized: view | project default |
| Project default | +materialized: table | dbt's built-in default |
This is why your staging model file contains only a SELECT: location does the configuring. It is also why putting a file in the right folder is a real decision — it sets materialisation, database, schema, tags and post-hooks in one move.
Tags drive the schedules
Those +tags are not decoration. The nightly build selects models by tag — daily-tagged layers rebuild every night; heavier marts run on their own cadence. You can use them too:
dbt build -s tag:daily # everything in the nightly selection
dbt ls -s tag:reporting # list models carrying a tagVariables: one value, overridable per run
Some values need to be consistent everywhere but changeable for a single run — reference dates are the project's main example. The QOF reference date is defined once and read through a macro; every register model calls {{ qof_reference_date() }} rather than hardcoding a date. To rebuild a register as of a different date, override it at the command line:
dbt build -s fct_person_diabetes_register --vars '{"qof_reference_date": "2025-03-31"}'One definition in normal use, an explicit override when you need to rewind — and the override is visible in the command, not hidden in an edited file.
Post-hooks: governance on autopilot
The project-level +post-hook runs after every model build — transferring ownership so the right roles can manage the object. Published models add hooks for Snowflake governance tags. You get all of this without writing a line: it is the layer doing the work.
Quiz
0/2 answered1A model file has config(materialized='view') but its folder sets +materialized: table. What builds?
2Moving a model file between layer folders can change…