Building a dbt project in a dev or branch environment usually rebuilds everything, even though most models are identical to production. Reuse from production avoids that:
- SQLBuild copies the already-built tables from a production-shaped git branch into your target.
- It seeds incremental models from production so they only need to catch up, not rebuild.
- dbt then builds only the models your branch actually changed.
This is the dbt-compatibility counterpart to SQLBuild’s native reuse from production, which reuses relations from another live target in the same warehouse. This version reuses the dbt-built tables described by a different git branch.
How it works
When reuse_from is configured, SQLBuild runs a reuse pre-phase before dbt executes:
- SQLBuild git-archives the configured
git_ref into an isolated temporary checkout (your working tree is never touched).
- It compiles that checkout’s dbt project on its own, producing a second manifest that describes the production-shaped relations.
- For each in-scope dbt model, SQLBuild compares the current manifest against the production manifest and the warehouse state, and decides whether the model can be reused.
- Reusable tables are copied into your target, and incremental models get a production baseline, before dbt runs. dbt then only builds the models that genuinely changed on your branch.
Because reuse runs as a pre-phase, the models it satisfies are already current by the time the change-aware dbt run starts, so they are pruned from the dbt command.
Configuration
Configure reuse in the [dbt] block of sqlbuild_project.toml:
[dbt]
project_dir = "../dbt_project"
profiles_dir = "../profiles"
target_path = "../dbt_project/target"
[dbt.reuse_from]
git_ref = "main"
generate_schema_name_override = "dbt/macros/generate_schema_name.sql"
| Field | Description |
|---|
git_ref | The production-shaped git branch or tag whose built tables you want to reuse. Must differ from the current branch. |
generate_schema_name_override | Relative path to a generate_schema_name macro that resolves to your production schema layout, used when compiling the reuse ref. Must live under dbt/macros/. See The schema-name override macro. |
The override macro is injected into the isolated checkout when SQLBuild compiles the reuse ref, so the production manifest resolves to the production warehouse relations rather than your dev schema.
The schema-name override macro
To reuse a production table, SQLBuild needs its exact warehouse location. It gets this by compiling the production git ref in isolation and reading the relation names from the resulting manifest, which dbt computes with its generate_schema_name macro.
The problem: SQLBuild compiles the ref with your configured target and deliberately knows nothing about your environments. If that target resolves to your dev schemas, the manifest points at dev tables, not the production ones you want to copy from.
The override fixes this:
- You provide a
generate_schema_name macro that resolves to your production schema layout, with no environment branching.
- SQLBuild injects it into the isolated checkout only, never your real dbt project.
- The macro must be named
generate_schema_name (so it shadows your project’s own) and live under dbt/macros/ in your SQLBuild project.
The rule: take your project’s existing generate_schema_name, keep only what it does in production, and delete the dev/CI branching.
Most projects have a macro that switches on the environment, building clean schema names in production and suffixed ones in dev or CI:
-- your dbt project's existing macro
{% macro generate_schema_name(custom_schema_name, node) -%}
{%- if target.name == 'prod' -%}
{%- if custom_schema_name is none -%}
{{ target.schema }}
{%- else -%}
{{ custom_schema_name | trim }}
{%- endif -%}
{%- else -%}
{{ target.schema }}_{{ target.name }}
{%- endif -%}
{%- endmacro %}
The override is just the production branch, with the conditional removed:
-- dbt/macros/generate_schema_name.sql
{% macro generate_schema_name(custom_schema_name, node) -%}
{%- if custom_schema_name is none -%}
{{ target.schema }}
{%- else -%}
{{ custom_schema_name | trim }}
{%- endif -%}
{%- endmacro %}
If your production uses dbt’s default unsuffixed layout (everything in the base schema, custom schemas concatenated), the override is just dbt’s stock generate_schema_name:
-- dbt/macros/generate_schema_name.sql
{% macro generate_schema_name(custom_schema_name, node) -%}
{%- if custom_schema_name is none -%}
{{ target.schema }}
{%- else -%}
{{ target.schema }}_{{ custom_schema_name | trim }}
{%- endif -%}
{%- endmacro %}
The override must produce production’s schema names regardless of which target SQLBuild compiles with. The examples above use target.schema as the base, which only resolves to production when your configured target’s schema is the production one. If your production base schema is a fixed value that differs from the active target, write that literal value instead of target.schema.
Reuse modes
SQLBuild picks the mode per model based on its dbt materialization.
Complete reuse (tables)
For table models, SQLBuild reuses the production table outright:
- Creates a staging table from the production relation.
- Promotes it atomically into the destination.
- Writes a fingerprint so the model counts as current.
dbt does not rebuild it.
Baseline reuse (incremental, microbatch, snapshot)
For incremental, microbatch, and snapshot models, SQLBuild seeds a baseline from production and lets dbt catch up:
- If the destination does not exist, it copies the production relation as the baseline.
- If the destination already has data, it appends only the rows past the destination’s current cursor value.
- dbt then runs the incremental model on top, catching up to the latest data.
The cursor column is read from the model’s meta.sqlbuild.reuse_cursor in the manifest:
models:
- name: fct_orders
config:
materialized: incremental
meta:
sqlbuild:
reuse_cursor: order_ts
What qualifies for reuse
Only physical materializations are reuse candidates: table, incremental, microbatch, and snapshot. The following are skipped:
| Materialization | Result |
|---|
view | skipped (no physical table to reuse) |
ephemeral | skipped (no relation) |
| anything else | skipped (unsupported materialization) |
A qualifying model is still rebuilt instead of reused when:
- a full refresh is requested,
- an upstream source is stale and blocks it, or
- its reuse metadata no longer matches the current relations or cursor.
Plan output
The reuse pre-phase reports what it pulled, separately from the dbt run:
dbt reuse pre-phase before dbt execution
model.analytics.fct_orders OK reuse
model.analytics.fct_daily_activity OK baseline reuse before dbt catch-up
REUSED=1 BASELINE_REUSED=1 TOTAL=2
REUSED counts complete table reuses.
BASELINE_REUSED counts incremental baselines that dbt will catch up on.
Requirements and guards
Reuse uses git to read the production ref, with guards that fail fast on misconfiguration:
- The SQLBuild project must be inside a git repository, and the dbt project directory must be under the same git root.
git_ref must not be the current branch. Choose a production-shaped branch or tag that differs from your active worktree branch.
- If
git_ref tracks a remote branch, SQLBuild refreshes it from the remote before archiving, so reuse reflects the latest production state.
git must be installed and available on PATH.
Distinct from target-level reuse
There are two reuse mechanisms with the same name, operating at different layers:
- dbt reuse (
[dbt].reuse_from, this page) reuses dbt-built tables described by a production git branch, with an isolated reuse compile.
- Native reuse (
targets.<name>.reuse_from, see Planning) reuses SQLBuild relations from another live target in the same warehouse, keyed on SQLBuild version identity, with optional zero-copy cloning.
They are configured independently and can be used in the same project for their respective model kinds.