What you will be able to do
- Explain why raw tables are declared as sources instead of being hard-coded in models
- Declare a source in YAML, including database, schema and identifier overrides, and predict what it compiles to
- Tell when to use source() and when to use ref(), and what each adds to the DAG
- Verify a model's raw dependencies with node selection, dbt ls and source tests
Key concept
Source dependency — When a model reads a raw table through source() instead of a hard-coded name, dbt records a DAG edge from that declared source to the model. That edge is what lets dbt show lineage, select resources by their upstream raw data, and resolve the real table name from a single YAML definition.
1.Why raw objects need declaring
Every dbt project starts from data that dbt did not build. Extract and Load tools write it into the warehouse, and because third parties load it, its shape changes without warning: tables and columns get added, removed or renamed. If ten models each hard-code raw.jaffle_shop.orders, a rename upstream means editing ten files. If they all go through one declared source, it means editing one YAML entry.
The recommendation is blunt. Define raw data as sources and select from the source, never from a direct relation reference such as my_schema.my_table. A well-kept dbt project has no direct relation references in any model. A hard-coded name still runs, which is why the habit lasts, but dbt cannot see it. It adds no edge to the DAG, the model has no recorded raw dependency, and nothing ties it to the tests or freshness checks defined on that data.
Sources also set where the first transformation layer begins. Each staging model should select from only one source. It renames fields to the project's conventions (for example, every timestamp named <event>_at) and recasts types. Everything downstream builds on those staging models, so each raw object is touched in exactly one place. How that layering carries on through intermediate and mart models belongs to [building clean DAGs](lesson:1.5).
Checkpoint 1 of 7· Check yourself
A loader team renames the raw table orders to api_orders. In which project does the fix take the least work?
With a source declaration, the rename is one identifier change in YAML. Every model calling source() picks it up when it compiles.
“When this happens, it is easier to update models if raw data is only referenced in one place.”Source: docs.getdbt.com
Sources1
2.Declaring a source and resolving its real name
You declare sources in .yml files under a top-level sources: key. These files can sit at any depth inside the models/ directory and can have any name. Each source has a name, and inside it a list of tables. Three properties map the friendly names you use in code onto the object that actually exists in the warehouse:
- database names the database the raw data lives in, which is common on Snowflake.
- schema defaults to the source's name, so you set it only when the two differ.
- identifier names the physical table when it differs from the table's name.
sources:
- name: jaffle_shop
database: raw
schema: postgres_backend_public_schema
tables:
- name: orders
identifier: api_ordersGiven that declaration, a model writes {{ source('jaffle_shop', 'orders') }} and dbt compiles it to raw.postgres_backend_public_schema.api_orders. The first argument is the source name and the second is the table name, never the physical identifiers. When you check a raw dependency, the compiled SQL is where you confirm that the declared names resolve to the object you expect. Reading compiled code in general is covered in [troubleshooting with compiled code](lesson:3.2).
Two details trip people up when they check that a raw object resolves correctly. First, dbt does not quote the database, schema or identifier by default. If a raw table was created with a case-sensitive name, set quoting (for example identifier: true) on the source or on a single table, and the table-level setting overrides the source-level one. Second, source properties are special. They live only in these .yml files, and you cannot set them from dbt_project.yml or a config() block.
Checkpoint 2 of 7· Fill the gap
Which function completes this model so that it records a dependency on the declared raw table?
select * from {{ ? ('jaffle_shop', 'orders') }}source() takes a source name and a table name, compiles to the full object name, and creates the DAG edge to the raw table.
Source: docs.getdbt.comCheckpoint 3 of 7· Exam question
Which of the following statements accurately describe how dbt's `source()` function affects dependency resolution and the project DAG? (Select all that apply.)(Select 3)
Correct answers: A, B, E — `source()` calls add source nodes as DAG entry points, letting `dbt run --select source:name+` execute every downstream model in one command.; `source()` requires the table to be declared under a `sources:` key in a YAML properties file before the Jinja function can resolve it during compilation.; `source()` enables freshness monitoring through `dbt source freshness`, which is not available for tables referenced through `ref()`.
- A. Declaring a source creates a graph entry point, and the `source:name+` selection syntax lets a team run or test everything downstream of that raw object in a single invocation.
- B. A table must first be documented under a `sources:` key in a properties YAML file; without that declaration dbt has no source node to resolve when the Jinja function is compiled.
- C. dbt never creates or manages the physical raw table behind a source declaration; a source only describes data that an external loader has already placed in the warehouse.
- D. The two functions resolve differently: `ref()` points at a model's configured schema and table, while `source()` points at the exact table declared in the source YAML, and dbt tracks them as distinct node types in the manifest.
- E. Freshness thresholds, `warn_after`/`error_after`, and the `dbt source freshness` command are only available for objects declared as sources; there is no equivalent freshness check for models referenced through `ref()`.
- F. The opposite is true: `source()` explicitly registers the raw table as a node in dbt's dependency graph, which is exactly what lets lineage, selection syntax, and freshness checks recognize it.
Sources2
3.source() versus ref(): two kinds of DAG edge
The two functions split the DAG along one line: who created the object. source() points at data that arrived from outside dbt. ref() points at a node dbt builds itself, which means a model, a seed or a snapshot. Both return a Relation, both compile to the full object name, and both create a dependency that dbt uses for documentation and node selection. They differ in what sits at the other end of the edge.
ref() also does two jobs that a raw reference never needs. It fills in the schema for whatever environment you are working in, so a model in your dev target reads the dev copy of its upstream model and not production. It also uses these references to build the dependency graph, which is how dbt run knows to build stg_orders before fct_orders. Point ref() at a raw table and it fails to resolve, because no node by that name exists. Hard-code a model's name instead of using ref() and you lose both the build order and the environment switch.
| Reference style | Points at | DAG edge created | Name resolved from |
|---|---|---|---|
| {{ source('jaffle_shop', 'orders') }} | Raw data loaded by an Extract and Load tool | Yes, source to model | database, schema and identifier in the sources: YAML |
| {{ ref('model_a') }} | A model, seed or snapshot dbt builds | Yes, node to model | The current target environment's schema |
| raw.jaffle_shop.orders (direct relation reference) | Whatever the string names | No | Nothing; the string is used as written |
Checkpoint 4 of 7· Match them up
Match each upstream object to the way a model should reference it
Tap a term, then the definition that fits it.
Loaded raw data goes through source(). Anything dbt builds goes through ref(), and the two-argument form is recommended across packages and projects.
“We recommend using two-argument ref any time you are referencing a model defined in a different package or project.”Source: docs.getdbt.com
Checkpoint 5 of 7· Exam question
A new raw table `events_v2` has replaced `events` in the warehouse, and several staging models still reference the old table through `source()`. Which actions correctly verify and update raw object dependencies for this change? (Select all that apply.)(Select 3)
Correct answers: A, B, E — Update the `identifier:` key on the existing source table entry to `events_v2` so `source()` calls keep the same friendly name while pointing at the new physical table.; Run `dbt list --select source:raw_events.events+` to confirm which downstream models are currently affected before making any change.; Run `dbt source freshness` after the update to confirm the new table's `loaded_at_field` is populating correctly under the existing freshness thresholds.
- A. The `identifier:` key exists precisely for this case: it lets the friendly source name stay stable in `source()` calls while pointing dbt at whichever physical table name currently backs it.
- B. Listing everything downstream of the affected source before touching it gives an accurate impact list, which is the correct first verification step before editing YAML or model code.
- C. A raw table is still a raw table after a rename; switching to `ref()` would incorrectly claim the table is a dbt-built model, which breaks compilation since no such model exists.
- D. dbt has no mechanism to infer a source declaration from warehouse metadata; deleting the block removes the dependency node entirely and breaks every `source()` call that referenced it.
- E. Re-checking freshness after the identifier update confirms the new table is loading correctly under the existing thresholds, closing the loop on the verification.
- F. dbt does not maintain a warehouse-side cache of table names that a cluster restart would refresh; source resolution is driven entirely by the YAML declaration, not by warehouse state.
4.Verifying raw dependencies before you build
Declared edges only help if you check them. Once every raw read goes through source(), the DAG can answer two questions: which raw objects does this model depend on, and which models break if this raw table changes? Node selection answers both. A + in front of a resource selects it plus all its ancestors. A + after it selects it plus all its descendants. The source: method selects by source name, or by source and table.
$ dbt run --select source:jaffle_shop.orders+Use dbt ls (alias dbt list) to look before you run anything. It takes the same selector arguments as dbt run but never connects to the database or runs a query. That makes it a cheap way to confirm that a model's lineage contains the sources you expect. Use --resource-type source to keep only sources in the output, and --output json when you need node properties. Two limits apply. Disabled models are left out of the results. And because dbt ls reads only the project, it checks the declared edges, not whether the raw table actually exists or holds good data.
Checking the data itself is a test's job. Sources accept the same data tests and descriptions as models, and you can run tests on them alone: dbt test --select "source:*" for every source, source:jaffle_shop for one source, or source:jaffle_shop.orders for a single table. Which assertions are worth making about raw data is covered in [testing assumptions for models and sources](lesson:5.2). Whether the raw data is arriving on time is a separate check, covered in [source freshness](lesson:6.2).
Checkpoint 6 of 7· Check yourself
Before a deploy, you want to list every raw table that fct_orders depends on, without spending warehouse compute. Which approach fits?
dbt ls resolves the selection (fct_orders and its ancestors), keeps only sources, and does not connect to the database. The other options run models or tests against the warehouse.
“this command will not connect to your database or run any queries.”Source: docs.getdbt.com
Checkpoint 7 of 7· Exam question
Put the following steps in the correct order for identifying and verifying a new raw object dependency before building a staging model on top of it.(Select 5)
Correct answers: A, B, C, D, E — Declare the raw table under a `sources:` key in a properties YAML file, including `database`, `schema`, and table name.; Add a `loaded_at_field` and freshness thresholds to the source declaration so staleness can be monitored.; Run `dbt source freshness` to confirm the raw table is loading on schedule before building anything on top of it.; Reference the raw table in the new staging model using `{{ source('name', 'table') }}` instead of a hardcoded table name.; Run `dbt build --select +staging_model_name` to compile and test the new model against its verified source dependency.
- A. The source must exist in YAML before anything can reference it, so declaring it with its physical location is the necessary first step.
- B. Once the source exists, adding freshness configuration is the next logical step so staleness can be monitored before the raw data is trusted for transformation.
- C. With freshness configured, checking it confirms the raw data is actually arriving on schedule before any model is built against it.
- D. After the raw table is declared and confirmed fresh, the staging model can safely reference it through `source()`, registering the dependency in the DAG.
- E. Building the model last, scoped with `+` to include its upstream dependency, is the step that compiles and tests the new model against the now-verified source.
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Hard-coding raw.jaffle_shop.orders in a model works exactly like calling source(), as long as the query runs.Why is that wrong?
A direct relation reference creates no DAG edge. The model drops out of lineage, out of source: selection and away from the tests on that source, and every model that hard-codes the name has to be edited when the raw table changes.
Covered in Why raw objects need declaring
2.ref() can point at any table in the warehouse, including raw loaded data.Why is that wrong?
ref() resolves only to nodes dbt builds (models, seeds and snapshots). Raw data from Extract and Load tools has to be declared as a source and read with source().
Covered in source() versus ref(): two kinds of DAG edge
3.A source's database or schema can be set project-wide in dbt_project.yml or in a model's config() block.Why is that wrong?
Source properties are special. They are declared only in .yml files under the models/ directory.
4.Running dbt ls confirms that the raw tables behind a model exist in the warehouse.Why is that wrong?
dbt ls reads the project and resolves selectors, but it never queries the database. It checks the declared edges, not the physical objects.
Covered in Verifying raw dependencies before you build
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“Our dbt projects don't contain any direct relation references in any models.”
↩︎ Why raw objects need declaring“Select from only one source”
↩︎ Why raw objects need declaring“Using the ref function allows dbt to infer dependencies, ensuring that models are built in the correct order.”
↩︎ source() versus ref(): two kinds of DAG edge“We recommend defining your raw data as sources, and selecting from the source rather than using the direct relation reference.”
↩︎ Exam trap 1“When this happens, it is easier to update models if raw data is only referenced in one place.”
↩︎ Checkpoint - 2.https://docs.getdbt.com/docs/build/sourcesOfficial docs
“Sources are defined in .yml files nested under a sources: key.”
↩︎ Declaring a source and resolving its real name“By default, schema will be the same as name.”
↩︎ Declaring a source and resolving its real name“By default, dbt will not quote the database, schema, or identifier for the source tables that you've specified.”
↩︎ Declaring a source and resolving its real name“To run models downstream of a source, use the source: selector:”
↩︎ Verifying raw dependencies before you build“Using the {{ source () }} function also creates a dependency between the model and the source table.”
↩︎ Key concept - 3.
“Creates dependencies between a source and the current model, which is useful for documentation and node selection”
↩︎ source() versus ref(): two kinds of DAG edge - 4.
“it is using these references between models to automatically build the dependency graph.”
↩︎ source() versus ref(): two kinds of DAG edge“Returns a Relation for a model, seed, or snapshot”
↩︎ Exam trap 2“Returns a Relation for a model, seed, or snapshot”
↩︎ Prediction“We recommend using two-argument ref any time you are referencing a model defined in a different package or project.”
↩︎ Checkpoint - 5.
“Includes the resource itself and all its ancestors (upstream dependencies).”
↩︎ Verifying raw dependencies before you build - 6.https://docs.getdbt.com/reference/commands/listOfficial docs
“Note that the dbt ls command does not include models which are disabled”
↩︎ Verifying raw dependencies before you build“this command will not connect to your database or run any queries.”
↩︎ Exam trap 4“this command will not connect to your database or run any queries.”
↩︎ Checkpoint - 7.https://docs.getdbt.com/faqs/Tests/testing-sourcesOfficial docs
“to run data tests on one source table only:”
↩︎ Verifying raw dependencies before you build
Also cited
“Source properties are "special properties" in that you can't configure them in the dbt_project.yml file or using config() blocks.”
↩︎ Exam trap 3