100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

ref() and source() Functions

Learn how dbt's ref() and source() functions build the project's dependency graph and enable environment-safe, portable SQL.

ModelsBeginner8 min readJul 10, 2026
Analogies

Why ref() Exists

The ref() function lets a model reference another dbt model by name instead of by a hardcoded, environment-specific schema and table name, and dbt resolves that reference at compile time into the correct fully-qualified path for whatever target you're running against — dev, staging, or prod. Because every ref() call is a dependency edge, dbt uses them to automatically build the DAG and determine the correct execution order, so a model referencing stg_orders will always run after stg_orders has been built.

🏏

Cricket analogy: This is like a captain calling for 'the death-overs specialist' rather than naming a specific player by name, so whichever bowler currently holds that role — even after a squad change — gets brought on automatically.

sql
-- models/marts/fct_orders.sql
select
    o.order_id,
    o.customer_id,
    c.email,
    o.order_total
from {{ ref('stg_orders') }} as o
left join {{ ref('stg_customers') }} as c
    on o.customer_id = c.customer_id

source() and Declaring Raw Data

The source() function points to raw, un-transformed tables that live outside of dbt's control — data loaded by a tool like Fivetran or an ELT pipeline — and it requires those tables to first be declared in a sources.yml file with a source name and table name. Declaring sources explicitly, rather than just writing raw table names into a FROM clause, lets dbt track raw data as a first-class node in the DAG, enabling source freshness checks and documentation alongside your models.

🏏

Cricket analogy: This is like officially registering a player's eligibility with the cricket board before they can be selected for the national team, rather than just having them show up and bat unofficially.

yaml
# models/staging/sources.yml
version: 2

sources:
  - name: raw
    database: analytics_raw
    schema: app_production
    tables:
      - name: orders
        loaded_at_field: _loaded_at
        freshness:
          warn_after: {count: 12, period: hour}
          error_after: {count: 24, period: hour}
      - name: customers

Run dbt source freshness to check whether your declared sources are being loaded on schedule — it compares the current time against loaded_at_field and warns or errors based on the thresholds in sources.yml.

How ref() and source() Power the DAG

Every ref() and source() call dbt encounters while parsing your project becomes an edge in a directed acyclic graph, which is what powers commands like dbt run --select stg_orders+ (run this model and everything downstream of it) or dbt run --select +fct_orders (run this model and everything it depends on). Because the graph is built purely from these function calls rather than manual configuration, adding a new dependency is as simple as adding a ref() — dbt automatically recalculates the correct build order the next time it parses the project.

🏏

Cricket analogy: This is like how a bowling attack's rotation naturally emerges from who's fit and available that match, rather than being fixed months in advance — the team management just reads the current dependencies and slots bowlers in correctly.

Never bypass ref() or source() by writing a raw, fully-qualified table name directly into a model's SQL — doing so silently breaks the DAG, meaning dbt can no longer guarantee correct run order or detect that dependency during selection.

  • ref() references another dbt model by name; dbt resolves it to the correct schema/table at compile time.
  • source() references raw, un-transformed tables declared in a sources.yml file.
  • Every ref()/source() call becomes an edge in dbt's dependency graph (the DAG).
  • The DAG determines execution order and powers graph-based selectors like +model_name and model_name+.
  • Declaring sources enables dbt source freshness checks and centralized documentation.
  • Hardcoding raw table names instead of using ref()/source() silently breaks dependency tracking.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#DbtDataBuildToolStudyNotes#RefAndSourceFunctions#Ref#Source#Functions#Exists#StudyNotes#SkillVeris#ExamPrep