What you will be able to do
- Say what dbt_project.yml must contain, and which project-level keys it sets
- Write resource paths that target every model, only your project, one folder, or a single model
- Use the + prefix correctly, and spell multi-word resource keys the way dbt_project.yml expects
- Work out the final value of a config set in several places, including tags, hooks and meta, which merge instead of replacing
Key concept
Hierarchical configuration — In dbt_project.yml, a config set at one level of the resource path applies to everything below that level. Any deeper or more specific setting overrides it. On top of that, a config in a resource's .yml properties or its config() block overrides anything set in the project file.
1.What dbt_project.yml is for
**dbt_project.yml is the one file every dbt project must have.** dbt treats a directory as a project only when this file is present. dbt searches the current working directory and its parents for it. To point dbt somewhere else, use the --project-dir flag or, from v1.11, the DBT_ENGINE_PROJECT_DIR environment variable. The file also stores the settings that control how dbt runs the project.
The file carries two kinds of setting. The first kind describes the project itself: its name, the connection profile, and where each resource type lives on disk. The second kind sits under resource keys such as models:, seeds:, snapshots:, sources: and data_tests:. These keys hold configs that dbt applies to many resources at once. The table below lists the project-level keys you will meet most often.
| Key | What it sets |
|---|---|
| name | Your project's name in snake case |
| profile | The profile dbt uses to connect to your data platform |
| model-paths | Directories where your model and source files live |
| seed-paths / macro-paths / snapshot-paths | Directories for seeds, macros and snapshots |
| require-dbt-version | Restricts the project to a range of dbt versions |
| vars | Project variables used during compilation |
Only configs belong under the resource keys. dbt separates properties, which describe a resource (descriptions, column tests, exposure types), from configs, which tell dbt how to build it (materialization, schema, tags, meta). You declare properties one resource at a time in .yml property files. You can set a config in three places: in a .yml file, in a config() macro inside the .sql file, or in dbt_project.yml for whole groups of resources. A non-config property, such as anything about macros, cannot go in the project file. This bulk setting is why teams organise models into folders: a folder-level config saves people from remembering to add a config block to every new model.
Checkpoint 1 of 8· Check yourself
A teammate wants to use dbt_project.yml to set something for a whole group of resources. Which of these can go there?
Materialization is a config, and configs can be set for many resources at once in dbt_project.yml. Descriptions, column tests and macro documentation are properties, which you declare one resource at a time in .yml files.
“Note, you can't set up a "property" in the dbt_project.yml file if it's not a config (an example is macros).”Source: docs.getdbt.com
2.Targeting resources with resource paths
A resource path is a set of nested YAML keys that matches your folder layout. Under models:, the first level can be your project name. Below that come directory names, then subdirectory names, and at the deepest level a single resource named after its file. How deep you place a config decides how much it affects. That makes the nesting level the first thing to check when a config hits too many models or too few.
The levels differ in exactly what they include. A config placed directly under models:, with no path, applies to every model dbt loads, including models from installed packages. Disabling everything this way is rarely what you want. A config nested under your project name applies to your own models and leaves package models alone. Add staging: below the project name and the config reaches only models/staging/. Spell out the full path down to a file name and it reaches a single model:
name: jaffle_shop
models:
jaffle_shop:
staging:
stripe:
payments:
+enabled: false # this will apply to only one modelNested levels let you set a broad default and then make a narrow exception. In the example below, every model under events/ becomes a view. The base/ subfolder inside it overrides that and becomes ephemeral. Folder-wide materialization defaults are the most common use of this pattern. What each materialization actually builds is covered in [core materializations](lesson:1.2).
models:
# Be sure to namespace your model configs to your project name
dbt_labs:
# This configures models found in models/events/
events:
+enabled: true
+materialized: view
# This configures models found in models/events/base
# These models will be ephemeral, as the config above is overridden
base:
+materialized: ephemeralSources use the same nesting. To disable a single source table defined in a YAML file inside a subfolder, list the subfolder, then the source name, then the table name, all under your project name in the sources: key.
Checkpoint 2 of 8· Check yourself
Your project is named jaffle_shop and has two packages installed. You need to disable every model you wrote, and only those. Where does +enabled: false go?
Nesting the config under the project name limits it to your own models. Placing it directly under models: would also disable the models in your installed packages.
“this will apply to all models in your project, but not any installed packages”Source: docs.getdbt.com
Checkpoint 3 of 8· Exam question
A team sets `materialized: view` for the `staging` directory in `dbt_project.yml`. One staging model also has `materialized: table` inside a `{{ config() }}` block at the top of its SQL file, and its `.yml` properties file sets `materialized: incremental` under a `config:` block. When `dbt run` builds that model, which materialization does dbt actually apply?
Correct answer: B — Table
- A. The project-level directory default is the least specific config source, so a more specific setting elsewhere in the project overrides it before the model builds.
- B. This is correct: the in-file `{{ config() }}` macro sits at the top of dbt's config precedence, so it wins over both the `.yml` properties file and the dbt_project.yml directory default.
- C. The `.yml` properties file's `config:` block outranks the project file but is still overridden by an in-file config macro on the same model, so this value is not the one applied.
- D. Nothing in the scenario sets this materialization at any level, so it cannot be the value dbt resolves for this model.
Sources4
3.The + prefix: telling configs apart from folders
**In dbt_project.yml, a leading + marks a key as a config, and a key without it is read as a folder or file name.** Configs and folder names sit in the same nested dictionaries, so dbt needs a way to tell staging: (a directory) from +schema: (a setting). Projects on config-version: 2 are expected to prefix every config key with +. Writing configs without the prefix is deprecated, and the prefix does not work at all in files that use config-version: 1.
Two situations actually break when the prefix is missing. The first is a config whose value is a dictionary, such as persist_docs. Without +, its relation: and columns: keys look like subfolders. The second is a folder whose name matches a config name, such as a models/tags/ directory next to the tags config. Folder and file names must not start with +. In dbt v2, a resource path that starts with + raises a warning, so rename any folder such as +my_folder.
Checkpoint 4 of 8· Fill the gap
Every model should default to a view, and models in marts/ should become tables. Which key completes the marts setting?
models:
+materialized: view
jaffle_shop:
marts:
? : tableInside dbt_project.yml, config keys take the + prefix so dbt does not read them as folder names. The config is called materialized, not materialization.
Source: docs.getdbt.comCheckpoint 5 of 8· Exam question
An engineer writes this snippet in `dbt_project.yml`: ``` models: jaffle_shop: marts: materialized: table finance: tags: ["finance"] ``` They expect every model under `marts/finance` to materialize as a table, but all of them build as views instead. Which line in this snippet is misconfigured?
Correct answer: A — The `materialized: table` line is missing the `+` prefix required for project-file model configs, so dbt ignores it as an unrecognized key.
- A. This is the fault: resource configs nested inside dbt_project.yml must be written as `+materialized` rather than `materialized`, otherwise dbt treats the line as a plain, unrecognized YAML key and falls back to its default view materialization.
- B. Nesting model configs by directory path any number of levels deep is exactly how dbt_project.yml is meant to be structured, so depth is not the problem here.
- C. Multiple config keys, such as a materialization setting and a tags list, can coexist at the same directory node without conflict, so this is not what breaks the build.
- D. The nested project name under `models:` must match the project's `name:` field for dbt to apply the configs at all, but the scenario gives no indication that these names disagree.
Sources5
4.Spelling multi-word resource keys
Resource keys with more than one word follow different spelling rules, and the rule depends on the resource type and the file. In dbt_project.yml, saved queries and semantic models are written with dashes: saved-queries: and semantic-models:. Data tests and unit tests use underscores in every file, including the project file: data_tests: and unit_tests:. In any YAML file other than dbt_project.yml, multi-word resource types use underscores, so the saved queries key in a properties file is saved_queries:.
| Resource type | Key in dbt_project.yml | Key in other YAML files |
|---|---|---|
| Saved queries | saved-queries | saved_queries |
| Semantic models | semantic-models | underscore form |
| Data tests | data_tests | data_tests |
| Unit tests | unit_tests | unit_tests |
Watch for this when copying blocks between files. A saved_queries: block pasted from a properties file into dbt_project.yml has the wrong key there. A data-tests: key written to look like semantic-models: is wrong in every file.
Checkpoint 6 of 8· Match them up
Match each key to the place where it is the correct spelling
Tap a term, then the definition that fits it.
Saved queries and semantic models use dashes only in dbt_project.yml. Data tests and unit tests use underscores everywhere, and other YAML files use underscores for every multi-word type.
“For data tests and unit tests, use underscores (_) everywhere, including in dbt_project.yml”Source: docs.getdbt.com
Sources1
5.Which value wins: precedence, clobbering and merging
The project file has the lowest precedence of the three places a config can live. When the same config is set in more than one place, dbt uses the most specific one. A config() block inside the model file beats the config: property in a .yml file, and both beat dbt_project.yml. Within dbt_project.yml, the most deeply nested path wins. Your root project's settings also beat the settings that ship inside installed packages. That lets you change a package's behaviour from your own dbt_project.yml without editing files that dbt deps would overwrite.
For most configs, overriding means replacing outright, which dbt calls clobbering. If dbt_project.yml sets +materialized: view for a folder and one model's config() says table, that model is a table and nothing of the view setting remains.
A few configs combine values instead of replacing them, and these exceptions are what questions about final values usually test. tags are additive, so a model keeps its project-level tags and adds its own. pre-hook and post-hook are additive too, so a project-wide hook still runs alongside a model's own hook. For freshness, a more specific key-value pair replaces only the less specific value that has the same key.
meta is shallow-merged, which catches people out with nested values. dbt combines the top-level keys from every level, but when the same top-level key appears at two levels, the more specific value replaces it whole. Suppose dbt_project.yml sets +meta with owner: "alice" and a dagster dictionary holding automation_condition: "eager", and a model sets meta with dagster: {asset_key: "my_key"}. The result keeps owner, but dagster contains only asset_key. The nested automation_condition is lost, because dbt never looks inside dagster to merge it.
Checkpoint 7 of 8· Check yourself
dbt_project.yml sets +meta: {owner: "alice", dagster: {automation_condition: "eager"}}. A model's .yml sets meta: {dagster: {asset_key: "my_key"}}. What is the model's final meta?
owner appears at only one level, so it is kept. dagster appears at both levels, so the model's dagster dictionary replaces the project's whole, without merging the keys inside it.
“meta dictionaries are shallow-merged, meaning dbt merges only the top-level keys and does not look inside nested dictionaries.”Source: docs.getdbt.com
Checkpoint 8 of 8· Exam question
A `dbt_project.yml` sets `+grants: {select: ['user_a', 'user_b']}` under `models:`. A specific model's `.yml` properties file sets `config: {grants: {select: ['user_c']}}`. Which statements correctly describe how dbt resolves the grants on that model? (Select all that apply.)(Select 3)
Correct answers: A, C, D — Only `user_c` receives `select` privilege here, since grants use clobber behavior where the more specific config replaces the less specific default.; Changing the property file's key to `+select` instead of `select` would make dbt add `user_c` to the existing grantees instead of replacing them.; Setting `select: []` in the property file would revoke every previously granted `select` privilege on that model, leaving no grantees.
- A. By default, grant privileges use merge-and-clobber behavior: the more specific config replaces the less specific one per privilege, so only the property file's grantee list applies.
- B. Grants do not merge automatically across levels by default; that additive behavior only happens when the privilege key itself is prefixed with a plus sign.
- C. Prefixing the privilege name with a plus sign switches the merge behavior from clobber to additive, so the more specific grantee list is added to the existing one rather than replacing it.
- D. An empty list for a privilege is the documented way to revoke every grantee for that privilege, since there is nothing left for dbt to grant.
- E. A resource-level config is exactly what is expected to take precedence over a project-level default for grants, the same way it does for other resource configs.
- F. The privilege key name itself, such as `select`, is what dbt reads; renaming it to `+grants` would break the config rather than enable an override.
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.A materialization set in dbt_project.yml overrides the one in a model's config() block, because the project file is the central source of truth.Why is that wrong?
It works the other way round. The in-file config() block wins over .yml properties, and both win over the project file.
Covered in Which value wins: precedence, clobbering and merging
2.Config keys need the + prefix wherever you set them, including inside config() and under a .yml config: property.Why is that wrong?
The + prefix applies only to configs under a resource key in dbt_project.yml. config() and the .yml config property do not use it.
3.A config placed directly under models: with no project name affects only the models in your own project.Why is that wrong?
With no resource path, the config applies to every model, including models from installed packages. To limit it to your own models, nest it under your project name.
Covered in Targeting resources with resource paths
4.Hooks set on a model replace the project-wide pre-hook or post-hook, just as a model's materialization replaces the folder default.Why is that wrong?
Hooks are additive, like tags, so hooks from the project level and the model level all apply. Most other configs are clobbered.
Covered in Which value wins: precedence, clobbering and merging
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.https://docs.getdbt.com/reference/dbt_project.ymlOfficial docs
“Every dbt project needs a dbt_project.yml file — this is how dbt knows a directory is a dbt project.”
↩︎ What dbt_project.yml is for“By default, dbt looks for the dbt_project.yml in your current working directory and its parents”
↩︎ What dbt_project.yml is for“For the multi-word resource types saved-queries and semantic-models, use dashes (-) in dbt_project.yml.”
↩︎ Spelling multi-word resource keys“For YAML files other than dbt_project.yml, use underscores (_) for multi-word resource types.”
↩︎ Spelling multi-word resource keys“The + prefix is used for configs only and applies to dbt_project.yml under the corresponding resource key.”
↩︎ Exam trap 2“Note, you can't set up a "property" in the dbt_project.yml file if it's not a config (an example is macros).”
↩︎ Checkpoint“For data tests and unit tests, use underscores (_) everywhere, including in dbt_project.yml”
↩︎ Checkpoint - 2.
“They can also be set one-by-one via a config() macro (right within .sql files), and for many resources at once in dbt_project.yml.”
↩︎ What dbt_project.yml is for - 3.https://docs.getdbt.com/docs/build/projectsOfficial docs
“Some bulk configurations are easier done at the directory level so people don't have to remember to do everything in a config block”
↩︎ What dbt_project.yml is for - 4.
“the nested dictionary keys that provide the path to a directory of that resource type, or a single instance of that resource type”
↩︎ Targeting resources with resource paths“this will disable all models (not a thing you probably want to do)”
↩︎ Targeting resources with resource paths“To apply a configuration to all models in your project only, use your project name as the <resource-path>:”
↩︎ Exam trap 3“this will apply to all models in your project, but not any installed packages”
↩︎ Checkpoint - 5.
“For projects using config-version 2, dbt expects configuration keys to use the + prefix. Specifying configurations without the + prefix is deprecated.”
↩︎ The + prefix: telling configs apart from folders“A config shares a key with part of a resource path, such as a directory of models named tags.”
↩︎ The + prefix: telling configs apart from folders“Currently, the only config which supports + for controlling config merge behavior is grants.”
↩︎ The + prefix: telling configs apart from folders - 6.https://docs.getdbt.com/reference/define-configsOfficial docs
“Configurations in your root dbt project have higher precedence than configurations in installed packages.”
↩︎ Which value wins: precedence, clobbering and merging“Whenever a more specific value is available, it will completely replace the less specific value.”
↩︎ Which value wins: precedence, clobbering and merging“Within the project file, configurations are also applied hierarchically. The most specific config always takes precedence.”
↩︎ Key concept“an in-file config() block --> properties defined in a .yml file --> config defined in the project file.”
↩︎ Exam trap 1“pre-hook and post-hook are also additive.”
↩︎ Exam trap 4“tags are additive.”
↩︎ Prediction“meta dictionaries are shallow-merged, meaning dbt merges only the top-level keys and does not look inside nested dictionaries.”
↩︎ Checkpoint - 7.https://docs.getdbt.com/reference/model-configsOfficial docs
“In this case, the model that's nested the deepest will have the highest priority.”
↩︎ Which value wins: precedence, clobbering and merging