Skip to main content
Beta. The PrefectDbtOrchestrator, ExecutionMode, TestStrategy, CacheConfig, and BuildPlan symbols currently live in a private module (prefect_dbt.core._orchestrator) and will move to public exports in a future release. The API is stable enough for production use, but import paths may change — watch the release notes for the public-export announcement.

Overview

The PrefectDbtOrchestrator gives Prefect full control over dbt DAG execution. Instead of running dbt build as a single batch operation, the orchestrator parses your manifest, computes execution waves, and runs each node or wave as a Prefect task — enabling per-node retries, Prefect-native concurrency control, automatic downstream skipping on failure, and fine-grained observability. If you instead want dbt to drive execution with Prefect observing from the outside, see PrefectDbtRunner.

Install

PrefectDbtOrchestrator first shipped in prefect-dbt 0.7.17, but this guide exercises APIs that landed later. Pin prefect-dbt>=0.7.23 to match every example on this page:
Feature floors, for reference: See the integration overview for adapter-specific extras.

Quick start

run_build() automatically runs dbt parse to generate manifest.json if none is found in the project’s target directory. Pass manifest_path directly to the orchestrator constructor to use a pre-built manifest (e.g. from a CI artifact).
When the default DbtCoreExecutor has to generate a manifest itself (no manifest.json at the target path and no manifest_path passed), it first runs dbt deps to install packages declared in packages.yml, then dbt parse. If you pass a pre-built manifest_path or supply a custom executor, this does not happen — make sure your image already has the required dbt packages installed. To keep the default executor but skip the automatic dbt deps:
Always pass settings= alongside a custom executor= — the orchestrator itself still uses settings.project_dir and settings.profiles_dir for selector resolution, source freshness, and artifact paths. Omitting it makes the orchestrator fall back to a default PrefectDbtSettings(), which only works when the current working directory already contains a valid dbt project and profile.
By default, run_build() raises DbtBuildFailed if any node finishes with "error" status, so a failing dbt build fails the enclosing flow run. Pass raise_on_failure=False to the orchestrator to inspect the results dict on partial failures instead.

Execution modes

PER_WAVE (default)

Each wave of independent nodes runs as a single dbt build invocation. Lowest overhead. Nodes inside a wave that finish with per-node artifacts still appear in the results dict with their real status — only the nodes that actually failed are marked "error" — but once any node in a wave fails, all downstream waves are skipped as a group.

PER_NODE

Each node becomes a separate Prefect task in its own subprocess. Failed nodes retry independently; other nodes in the same wave continue; downstream dependents of failed nodes are skipped.
retries and cache are only supported in PER_NODE mode. Passing either with PER_WAVE raises ValueError.

Result format

run_build() returns a dict mapping unique_id to a result. Each result has a "status" of "success", "cached", "error", or "skipped":

Test strategies

The test_strategy parameter controls when dbt tests run relative to models. The default is IMMEDIATE, matching dbt build semantics.
Use DEFERRED when you want to see all model results before deciding whether to run tests, or to parallelize model execution more aggressively. Use SKIP for fast iteration when you want to skip test overhead.

Caching

Enable cross-run caching to skip nodes whose SQL, config, and upstream dependencies haven’t changed since the last successful run. Only available in PER_NODE mode. Pass a CacheConfig instance to consolidate all caching options:
The cache key for each node is derived from:
  • the hash of the node’s source SQL (or CSV for seeds) — not the compiled SQL, so config changes that only affect compilation via vars or environment will not invalidate the key on their own
  • the node’s dbt config
  • the node’s relation name
  • the full_refresh flag
  • the macros the node depends on (each macro’s content is hashed and folded in)
  • upstream nodes’ cache keys (changes cascade downstream)
When a cached result is valid, the node’s task returns immediately without invoking dbt. Cached results appear in the result dict with "status": "cached" (see Result format above).

CacheConfig fields

Custom cache storage

Block slugs follow the format "block-type/block-name" (for example "s3-bucket/my-results"). The block must be saved in Prefect before use. See How to persist workflow results for a walkthrough on creating and saving storage blocks.

Cache expiration from source freshness

When use_source_freshness_expiration=True, each node’s cache TTL is derived from the freshness thresholds of its upstream sources — a node backed by an hourly source won’t be cached longer than the source’s warn threshold.
use_source_freshness_expiration overrides the global expiration on a per-node basis.

Dry-run with plan()

Preview what run_build() would execute without actually running dbt models. The plan() method performs manifest parsing, selector resolution, and wave computation, but only invokes dbt ls (for selector resolution) and dbt source freshness (when enabled) — no models are built.
plan() is not free on the first call. With the default DbtCoreExecutor, if no manifest.json exists at the target path and no manifest_path was passed, the executor will run dbt deps followed by dbt parse before wave computation. To make plan() cheap, point manifest_path at a pre-built manifest (e.g. a CI artifact).
The returned BuildPlan includes: plan() accepts the same parameters as run_build(): select, exclude, full_refresh, only_fresh_sources, target, and extra_cli_args.

Artifacts and asset tracking

Summary artifact

When invoked from inside a flow run, run_build() creates a Prefect markdown artifact summarizing results — nodes executed, statuses, and timings. You can optionally write a dbt-compatible run_results.json:
include_compiled_code=True does not affect the summary artifact — the artifact is rendered from the results dict alone. The flag currently only adds compiled SQL to the asset descriptions attached to each MaterializingTask in PER_NODE mode.
Output locations:
  • The summary artifact is created with key dbt-orchestrator-summary. Creation requires an active flow run context — if you call run_build() from a plain Python script (supported in PER_WAVE mode), the summary step is skipped even with create_summary_artifact=True.
  • run_results.json is written inside settings.target_path (defaults to <project_dir>/target/run_results.json, but moves with target_path or a custom manifest_path).
To access the artifact programmatically, filter by key via ArtifactFilterread_artifacts ignores unknown kwargs, so a bare key=... argument silently returns every artifact in the workspace:
The artifact key dbt-orchestrator-summary is fixed and not currently configurable.

Prefect asset lineage

In PER_NODE mode, each model, seed, and snapshot creates a Prefect asset and tracks upstream lineage automatically — integrating dbt’s data lineage into Prefect’s asset catalog. Assets are named using the relation name (for example snowflake://my_db/analytics/stg_users). Upstream assets, including dbt sources, are tracked as asset dependencies. To disable asset creation:

run_build() parameters

target

Override the dbt target at run time without changing profiles.yml:

extra_cli_args

Pass additional CLI flags directly to dbt invocations. Useful for flags not exposed as first-class orchestrator parameters:
Some flags are blocked because they conflict with the orchestrator’s internal logic and passing them raises ValueError:--select, --models, --exclude, --selector, --indirect-selection, --project-dir, --target-path, --profiles-dir, --log-level.Others are first-class parameters — use the orchestrator directly instead:Caveat flags (--resource-type, --exclude-resource-type, --fail-fast) are accepted but log a warning because they can silently interact with orchestrator-managed test scheduling.

State-based execution (CI/CD)

Use dbt’s state flags to run only modified models and defer to production for everything else:
Also supports defer_state_path and favor_state for more advanced deferral scenarios.

dbt Cloud execution

The DbtCloudExecutor lets you run dbt nodes via dbt Cloud ephemeral jobs instead of local dbt-core. The orchestrator creates a temporary job in dbt Cloud for each node (or wave), polls until completion, fetches results, and deletes the job — giving you all orchestrator features (retries, caching, test strategies, assets) backed by dbt Cloud’s managed infrastructure.

Credentials setup

Create or load a DbtCloudCredentials block with your dbt Cloud API key and account ID:

Quick start

Even with a DbtCloudExecutor, the orchestrator still reads settings.project_dir and settings.profiles_dir for dbt build --select resolution, source-freshness handling, and artifact paths. Pass an explicit settings=PrefectDbtSettings(...) pointing at a local checkout of the same project — omitting it only works when the flow already runs inside a valid local dbt project with a usable default profile. The orchestrator’s state_path, defer, defer_state_path, and favor_state parameters, however, are specific to DbtCoreExecutor and have no effect when a custom executor is used.

Finding your dbt Cloud IDs

  • account_id — Settings → Account Settings → Account ID (also visible in your dbt Cloud URL: cloud.getdbt.com/deploy/{account_id}/...).
  • project_id — Deploy → Jobs → select any job → the URL contains /projects/{project_id}/.
  • environment_id — Deploy → Environments → select the target environment → the URL contains /environments/{environment_id}.
  • defer_to_job_id — Deploy → Jobs → select your production job → the URL ends with /jobs/{job_id}.

Parameter reference

Manifest resolution

The executor needs a manifest.json to parse the dbt DAG. How it gets one depends on defer_to_job_id:
  • defer_to_job_id is set — fetches manifest.json from the most recent successful run of that job via GET /jobs/{job_id}/artifacts/manifest.json. This is the recommended approach for production — point it at your production job to reuse its compiled manifest.
  • defer_to_job_id is None — creates an ephemeral dbt compile job, runs it, fetches the resulting manifest, and deletes the job. Useful for CI or when you need a fresh manifest, but adds compilation time.
For the fastest builds, set defer_to_job_id to your production job. This avoids the ephemeral compile step and reuses the manifest your production environment already generates.

Performance notes

  • Connection pooling. In PER_NODE mode, dbt adapter connections are automatically pooled and reused across sequential node invocations within each worker process, reducing connection overhead.
  • Eager DAG scheduling. In PER_NODE mode, nodes are submitted to the task runner as soon as all their individual dependencies complete, rather than waiting for wave boundaries. This maximizes concurrency without any configuration changes.

Comparison with PrefectDbtRunner

Both coexist in the same project — PrefectDbtRunner is unchanged. See PrefectDbtRunner for its dedicated guide.