Data Engineering  /  dbt

๐Ÿ”„ dbt โ€” Data Build Tool 60 guides ยท updated 2026

Analytics engineering with SQL โ€” models, tests, sources, and Jinja macros that turn raw warehouse tables into trustworthy, documented data products.

dbt Variables with var(): Parameterizing Models the Right Way

Hardcoding a date cutoff, a threshold, or a business rule directly into a modelโ€™s SQL works fine until the day it needs to change โ€” and then youโ€™re hunting through every model that has that value baked in, hoping you find all of them. dbtโ€™s var() function exists to pull values like this out of your SQL and into a single, declared, overridable place.


Declaring Variables

Variables are declared with defaults in dbt_project.yml, giving every model in the project a shared, single source of truth.

dbt_project.yml
vars:
start_date: '2024-01-01'
churn_threshold_days: 30
excluded_customer_ids: [1001, 1042, 2087]

Reading Variables in a Model

Inside any model, var() retrieves the value by name:

select *
from {{ ref('stg_orders') }}
where order_date >= '{{ var("start_date") }}'
and customer_id not in (
{% for id in var("excluded_customer_ids") %}
{{ id }}{% if not loop.last %},{% endif %}
{% endfor %}
)

Because var() returns whatever type the variable was declared as โ€” a string, a number, a list โ€” it composes naturally with the loops and conditionals covered in Jinja Templating.


Overriding Variables at Run Time

The real power of var() isnโ€™t the default in dbt_project.yml โ€” itโ€™s that any run can override it from the command line without touching a single file.

Terminal window
# Uses the default from dbt_project.yml
dbt run --select customer_orders
# Overrides start_date just for this run
dbt run --select customer_orders --vars '{"start_date": "2025-06-01"}'

This is what makes backfills, one-off reprocessing, and environment-specific behavior possible without maintaining separate copies of a model. A backfill job for a specific historical window is just a different --vars flag on the same command everyone else runs.


Providing a Fallback Default Inline

var() accepts a second argument that acts as a fallback if the variable isnโ€™t declared anywhere at all โ€” useful for optional configuration that most environments donโ€™t need to think about.

select *
from {{ ref('stg_events') }}
where event_date >= '{{ var("lookback_start_date", "2020-01-01") }}'

If lookback_start_date isnโ€™t set in dbt_project.yml or passed via --vars, this falls back to '2020-01-01' instead of raising a compilation error โ€” the safer default when a variable is genuinely optional rather than required.


Variables vs. Environment Variables

Itโ€™s easy to conflate var() with env_var(), but they solve different problems. var() is for values that change per-run or per-purpose within a single dbt invocation (a date cutoff, a feature flag). env_var() is for values tied to the execution environment itself โ€” most commonly secrets and connection details that shouldnโ€™t live in version control at all.

-- var(): a business-logic parameter, safe to commit
where order_date >= '{{ var("start_date") }}'
-- env_var(): typically used in profiles.yml for credentials, not business logic
password: "{{ env_var('DBT_WAREHOUSE_PASSWORD') }}"

A useful rule: if the value could reasonably be committed to Git, itโ€™s a var(). If itโ€™s a secret or environment-specific infrastructure detail, itโ€™s an env_var().


A Practical Pattern: Feature-Flagging a Model Change

Variables are a clean way to roll out a risky model change gradually, without maintaining two parallel model files.

{% if var("use_new_attribution_logic", false) %}
select customer_id, new_attribution_model(events) as attributed_channel
from {{ ref('stg_events') }}
{% else %}
select customer_id, legacy_attribution_channel as attributed_channel
from {{ ref('stg_events') }}
{% endif %}

Running with --vars '{"use_new_attribution_logic": true}' in a staging environment lets you validate the new logic before flipping the default in dbt_project.yml for everyone.


Variables Scoped to a Specific Model

Global vars in dbt_project.yml apply everywhere by default, but you can scope overrides to specific model paths if only part of the project needs a different value.

models:
my_project:
finance:
+vars:
start_date: '2023-01-01'

This is useful when different domains of the project genuinely need different defaults for the same variable name, without every model author needing to remember to pass --vars manually for that folder.


Variables in Tests and Macros, Not Just Models

var() isnโ€™t limited to model files โ€” it works identically inside custom generic tests and macros, which is useful for making a shared threshold configurable project-wide instead of hardcoded inside a test macro.

-- macros/test_value_within_range.sql
{% test value_within_range(model, column_name) %}
select *
from {{ model }}
where {{ column_name }} < {{ var("min_acceptable_value", 0) }}
or {{ column_name }} > {{ var("max_acceptable_value", 1000000) }}
{% endtest %}

This lets a single test macroโ€™s tolerance be tuned per environment or per run via --vars, without editing the macro itself โ€” the same overridability that makes var() valuable in models extends cleanly into the testing layer.

Common Mistakes

Using var() for secrets. A variable declared in dbt_project.yml is committed to Git in plain text. Passwords, API keys, and connection strings belong in env_var() reading from actual environment variables, never in vars.

Forgetting the default causes a hard failure. Calling var("some_value") with no default and no declaration anywhere raises a compilation error the moment that model is parsed โ€” always provide a second-argument fallback for genuinely optional variables.

Overusing variables for things that should just be ref(). A variable that holds a hardcoded table name is usually a sign the underlying data should have been declared as a proper source or model instead.

Summary

ElementPurpose
vars: in dbt_project.ymlDeclares project-wide default values
var("name")Reads a variable inside a model
var("name", default)Reads a variable with an inline fallback
--vars '{"key": "value"}'Overrides variables for a single CLI invocation

var() turns one-off hardcoded values into declared, overridable parameters โ€” the mechanism that makes backfills, feature flags, and environment-specific behavior possible without forking your models.