dbt

dbt config() Function: Guide to Overriding Model Settings In-Model

2026-04-19
NicheeLab Editorial Team

In dbt you can define the same setting key in multiple places. Knowing where it lives and how it gets overridden is critical for both the Analytics Engineer exam and day-to-day work.

This article walks through the basics of overriding settings inside a model with the config() function, the precedence order, gotchas for incremental models, environment-based switching, when to use YAML vs. config(), and exam-prep angles — all from a practical perspective.

config() Basics and Override Precedence

config() is the mechanism for declaring settings inside a model file via a Jinja block. Typical examples include materialized, schema, alias, tags, and incremental-related keys such as unique_key and on_schema_change.

When the same key is defined in multiple places, the general precedence is: low: dbt_project.yml → medium: resource properties YAML (the model's .yml) → high: in-model config(). Once you have this order in your head, you never have to guess where to override.

  • Because config() resolves during Jinja evaluation, declaring it at the top of the model SQL is the safe choice
  • Overrides happen per key. Unspecified keys inherit from the lower-precedence layer
  • Treat CLI flags (e.g., --full-refresh) as special cases that override certain behaviors separately
WhereExampleTypical usePrecedence (low → high)
dbt_project.ymlmodels: my_project: +materialized: viewProject-wide or per-directory defaultsLow
Resource YAML (properties)models: - name: fct_orders config: materialized: tableDeclarative per-model managementMedium
In-model config(){{ config(materialized='incremental', schema='mart') }}Urgent one-off overrides or conditional logicHigh

Override precedence (higher = stronger)

In-model config()Highest priorityResource YAML (properties)dbt_project.ymlDefaultsHigher in the list = higher precedence

Minimal config() sample

{{ config(
    materialized='table',
    schema='mart',
    alias='orders_daily'
) }}

select * from {{ ref('stg_orders') }}

Settings Keys You'll Override Most Often

The settings you'll override via config() most often are materialized, schema, alias, tags, and persist_docs (persisting column descriptions). Choose them with documentation and operations in mind.

schema and alias in particular tie directly into naming conventions and operational flow. Stick to project defaults and override only exceptional models in-model — that's the realistic approach.

  • materialized: view, table, incremental, etc.
  • schema/alias: useful for exceptional naming/placement cases
  • tags: handy for selective runs and ownership management
  • persist_docs: pushes descriptions into column comments (only on supporting warehouses)

Example with common config() keys

{{ config(
    materialized='view',
    schema='sandbox',
    alias='vw_customer_latest',
    tags=['customer', 'sla_daily'],
    persist_docs={'relation': true, 'columns': true}
) }}

select * from {{ ref('stg_customer') }}

Overriding config() in Incremental Models

For incremental models, in addition to materialized='incremental', you set unique_key, the strategy, on_schema_change, and so on appropriately. Warehouse-specific strategy options (e.g., merge, insert_overwrite) depend on what each warehouse supports.

unique_key defines row identity, and with the merge strategy, controlling which columns are updated (e.g., merge_update_columns) and how schema changes are handled (on_schema_change) really matters. On the exam, the role of these keys and the override precedence come up often.

  • unique_key: identifies rows uniquely (whether it's required depends on the strategy and warehouse)
  • incremental_strategy: typically merge, insert_overwrite, etc.
  • on_schema_change: ignore / append_new_columns / fail, etc. (warehouse-dependent support)

Template config() for incremental models

{{ config(
    materialized='incremental',
    unique_key='order_id',
    incremental_strategy='merge',
    on_schema_change='append_new_columns'
) }}

with src as (
  select * from {{ ref('stg_orders') }}
)
select * from src

{% if is_incremental() %}
  -- Example: only the last 90 days
  where order_date >= dateadd(day, -90, current_date)
{% endif %}

Switching config() by Environment

Conditional logic with target.name and var() is the go-to pattern in practice for switching schemas or materialization strategies between dev, staging, and production. Since Jinja is evaluated at parse time, keep the conditions self-contained in the config() block at the top of the model.

Excessive branching hurts readability. Set project defaults in dbt_project.yml and override only the exceptions in config() — that's the easiest pattern to maintain.

  • Control per-environment schemas via target.name
  • Use var() to inject minor behavior changes (always provide a default)
  • Keeping conditions inside config() makes them easy to trace

Example of environment-based switching

{% set is_heavy = var('heavy_model', false) %}
{% if target.name == 'prod' %}
  {{ config(schema='mart', materialized= 'table' if is_heavy else 'view') }}
{% else %}
  {{ config(schema=target.name ~ '_mart', materialized='view') }}
{% endif %}

select * from {{ ref('stg_heavy_fact') }}

YAML vs. In-Model config(): When to Use Which

Settings that change rarely and are descriptive in nature (e.g., persist_docs, tags, owners, contracts) are clearer when pushed into YAML. On the other hand, temporary or exceptional overrides closely tied to execution strategy are easier to manage in-model with config().

Exam questions often ask "where should this go?" Organizing it as three layers — project defaults in dbt_project.yml, model-specific static metadata in YAML, environment/strategy exceptions in config() — gives you a stable answer pattern.

  • Static metadata and descriptions in YAML; behavioral exceptions in config()
  • Consolidate conventions in dbt_project.yml and minimize deviations
  • During review, check the model's top config() and YAML together as a set

Side-by-side YAML and config() example

# models/schema.yml
models:
  - name: fct_orders
    description: Orders fact table.
    config:
      tags: ['finance']

-- models/fct_orders.sql
{{ config(materialized='incremental', unique_key='order_id') }}
select * from {{ ref('stg_orders') }}

Exam Prep and Practical Best Practices

The Analytics Engineer exam asks about precedence rules, required keys for incremental models, the meaning of on_schema_change, and where config() should be placed. Warehouse-specific options, when they appear, are usually framed so that general knowledge is enough.

In practice, what works is making project defaults strong, keeping exceptions in the model, and using PR review to surface intent (why the override is needed) via comments.

  • Precedence: dbt_project.yml < YAML properties < in-model config()
  • Incremental: be able to explain the meaning of unique_key and how it pairs with each strategy
  • Declare config() in the Jinja block at the top of the model and keep conditionals minimal

The minimal-override pattern that wins code reviews

{{ config(materialized='incremental', unique_key='id') }}
-- Let dbt_project.yml/YAML own defaults like schema/alias/tags,
-- and override only the keys directly tied to strategy here
select * from {{ ref('stg_dim_user') }}

Check Your Understanding

Analytics Engineer

問題 1

Your organization's default is that everything under models is a view, but you want one specific model to be a table. Which approach overrides it at the highest precedence with the smallest blast radius?

  1. Declare config(materialized='table') at the top of the model SQL
  2. Change models to table in dbt_project.yml and revert exceptions back to view in YAML
  3. Set materialized: table in the resource YAML and do nothing in the SQL
  4. Run with --full-refresh on the CLI

正解: A

Precedence is dbt_project.yml < resource YAML < in-model config(). To override at the highest precedence with the smallest blast radius, the correct answer is to set materialized='table' in the in-model config(). --full-refresh is runtime recreate behavior, not a persistent materialization-type setting.

Frequently Asked Questions

Where in the model file should config() be placed?

Because it is resolved during Jinja evaluation, the recommended placement is at the very top of the model SQL as a top-level Jinja block. Putting it later hurts readability and increases the risk of accidentally introducing conditional branches.

If the same key is set in both config() and YAML, which one wins?

The usual precedence is dbt_project.yml < resource YAML < in-model config(). When the same key is defined in both, the in-model config() takes effect.

Is unique_key always required for incremental models?

It depends on the strategy and warehouse. With the merge strategy, you typically specify unique_key to match rows for updates. Check the target warehouse documentation and the official dbt documentation for what is supported.

Check what you learned with practice questions

Practice with certification-focused question sets

Try free practice questions
Author

NicheeLab Editorial Team

NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.


Related articles
dbt

dbt Models: SQL-Defined Transformation Units (2026)

Model fundamentals — SELECT-based definitions, naming, refs,...

dbt

dbt Analytics Engineering Exam: Complete Guide (2026)

Pass the AE Certification — scope, weighting, sample questio...

dbt

dbt Cloud vs dbt Core: Feature & Cost Comparison (2026)

Honest comparison of dbt Cloud vs. dbt Core — IDE, scheduler...

dbt

dbt Project Structure: models/seeds/macros Layout (2026)

Recommended dbt project layout — models, seeds, macros, snap...

dbt

dbt_project.yml Explained: Every Config (2026)

Every dbt_project.yml setting that matters — paths, vars, ma...

Browse all dbt articles (101)
© 2026 NicheeLab All rights reserved.