Both are Jinja functions that point at upstream data, but they mean different ownership.
source()
Points at raw tables you do not build with dbt (loaded by Fivetran, Airflow, CDC jobs, etc.). You declare them in a YAML sources: block.
ref()
Points at another dbt model. dbt owns that relation and uses ref for the DAG and environment resolution.
-- staging reads a source
select * from {{ source('stripe', 'payments') }}
-- mart reads a model
select * from {{ ref('stg_payments') }}Diagram
source(stripe.payments) --> stg_payments --> fct_payments
(not dbt-built) ref(...) ref(...)Why declare sources?
- Central place for database/schema/table names
- Source freshness checks (
dbt source freshness) - Documentation and lineage from raw → staging → marts
sourcefailures are clearer than opaque hardcoded table strings
Rule of thumb
Outside dbt → source() Inside dbt → ref()
Never ref a raw landing table you did not create with a dbt model. Never source a mart that dbt builds.
Interview tip: Interviewers love this distinction. Say "source is the boundary with the rest of the platform; ref is how dbt models talk to each other."