dbt ref() Function
The ref() function is the core of how dbt models connect to each other. Every time one model needs data from another dbt model, you use ref(). This function does three things at once: it resolves the correct table name, it records the dependency between models, and it ensures dbt runs models in the right order.
The Simplest Use of ref()
-- models/fct_orders.sql
select
order_id,
customer_id,
amount
from {{ ref('stg_orders') }}
The string inside ref() is the name of another model file — without the .sql extension. dbt finds the file models/stg_orders.sql, looks up the schema where it lives, and replaces {{ ref('stg_orders') }} with the real table path.
What ref() Resolves To
The resolution depends on your current target environment:
Model file: models/stg_orders.sql
Target schema (dev): dbt_alice
{{ ref('stg_orders') }} → dbt_alice.stg_orders
Target schema (prod): analytics
{{ ref('stg_orders') }} → analytics.stg_orders
Switching from dev to prod changes every ref() resolution automatically. You never touch the SQL inside your model files to switch environments.
ref() Builds the DAG
Every ref() call tells dbt about a dependency. dbt collects all these dependencies and builds the directed acyclic graph (DAG) before running anything.
stg_customers <──── fct_orders ────> report_daily_sales
stg_orders <────┘
stg_products <──── int_order_items
|
v
fct_orders
dbt runs parent models before child models. You never worry about run order because ref() handles it.
ref() in a JOIN
Most real models join two or more upstream models. Each one uses a separate ref() call:
-- models/fct_orders.sql
select
o.order_id,
c.full_name as customer_name,
p.product_name,
o.quantity,
o.quantity * p.unit_price as line_total
from {{ ref('stg_orders') }} o
join {{ ref('stg_customers') }} c on o.customer_id = c.customer_id
join {{ ref('stg_products') }} p on o.product_id = p.product_id
dbt records three dependencies for fct_orders: it depends on stg_orders, stg_customers, and stg_products. All three run first.
ref() vs source()
A common point of confusion is when to use ref() and when to use source():
source('name', 'table') → use for raw tables that dbt did NOT create
(loaded by Fivetran, Airbyte, custom ETL, etc.)
ref('model_name') → use for tables that dbt DID create
(other models in your models/ folder)
-- Correct pattern
-- stg_orders reads from raw source
select * from {{ source('ecommerce', 'orders') }}
-- fct_orders reads from stg_orders (a dbt model)
select * from {{ ref('stg_orders') }}
Referencing Models in Other Folders
Models in subfolders do not need the folder path in the ref() call. The model file name is enough because dbt requires all model names to be unique across the entire project.
-- models/marts/fct_revenue.sql
-- stg_orders lives in models/staging/ but you just write:
from {{ ref('stg_orders') }}
If two model files have the same name in different folders, dbt raises an error during parsing. Keep all model names unique.
Cross-Project ref()
dbt supports referencing models from other dbt projects using a two-argument version of ref(). This feature is called cross-project references and requires dbt Mesh (a dbt Cloud feature).
-- Reference a model from another project
from {{ ref('finance_project', 'dim_accounts') }}
This advanced use case is relevant for large organizations that split dbt work across multiple teams and projects.
Using ref() for Selection
You can combine ref() logic with dbt's --select flag to run a model and everything it depends on:
# Run fct_orders and all its parents dbt run --select +fct_orders # Run stg_orders and everything downstream dbt run --select stg_orders+ # Run both directions dbt run --select +fct_orders+
What Happens When a ref() Target Fails
If stg_orders fails during a run, dbt automatically skips every model that depends on it. You see those models marked as SKIP in the terminal output. This prevents downstream models from running on incomplete or broken data.
1 of 3 ERROR stg_orders ............... [ERROR in 0.4s] 2 of 3 SKIP fct_orders ............... [SKIP] 3 of 3 SKIP report_revenue ........... [SKIP]
Fix the root cause in stg_orders and rerun. The dependent models then execute successfully.
Summary: ref() Rules
- Always use
ref()when reading from another dbt model — never hardcode table names - Use the model file name without the
.sqlextension as the argument - dbt automatically resolves the correct schema based on your current target
- Model names must be unique across your entire project
- ref() calls build the DAG, which determines run order
