Data Engineering  /  dbt

๐Ÿ”„ dbt โ€” Data Build Tool 60 guides ยท updated 2026

Analytics engineering with SQL โ€” models, tests, sources, and Jinja macros that turn raw warehouse tables into trustworthy, documented data products.

dbt Artifacts Explained: manifest.json, run_results.json, and More

Every time dbt compiles, runs, tests, or generates documentation, it writes structured JSON files to the target/ directory describing exactly what it did. These artifacts are the machine-readable layer underneath everything covered so far in this series โ€” the documentation site, the lineage graph, and increasingly, external tooling that integrates with dbt without needing to parse SQL directly.


The Core Artifacts and What Each One Contains

ArtifactProduced byContains
manifest.jsoncompile, run, docs generate, most commandsFull project structure: models, sources, tests, macros, dependencies, configuration
run_results.jsonrun, test, build, seed, snapshotPer-node execution results: status, timing, rows affected, compiled SQL
catalog.jsondocs generateActual warehouse metadata: column names, types, table statistics
sources.jsonsource freshnessFreshness check results per source table

Each is a plain JSON file, human-readable if you open one directly, but really designed to be consumed programmatically.


manifest.json: The Projectโ€™s Full Blueprint

This is the most information-dense artifact โ€” a complete representation of every node in your project and how they connect.

Terminal window
dbt compile
cat target/manifest.json | jq '.nodes | keys' | head -5
[
"model.my_project.stg_orders",
"model.my_project.stg_customers",
"model.my_project.customer_orders",
"test.my_project.unique_stg_orders_order_id",
"source.my_project.raw.orders"
]

Any tool that needs to understand your dbt projectโ€™s structure without parsing SQL directly โ€” a custom internal dashboard, a lineage visualization tool, a CI script checking for orphaned models โ€” reads manifest.json rather than trying to reimplement dbtโ€™s own dependency parsing.


run_results.json: What Actually Happened

While manifest.json describes structure, run_results.json describes outcomes โ€” what ran, whether it succeeded, how long it took.

Terminal window
dbt run
cat target/run_results.json | jq '.results[] | {unique_id, status, execution_time}'
{"unique_id": "model.my_project.stg_orders", "status": "success", "execution_time": 1.24}
{"unique_id": "test.my_project.unique_stg_orders_order_id", "status": "fail", "execution_time": 0.41}

This is the artifact most orchestration integrations care about most โ€” itโ€™s exactly what tells an Airflow DAG or a monitoring dashboard whether the run actually succeeded and where time was spent, covered in more depth in Artifacts in Orchestration.


catalog.json: What the Warehouse Actually Has

Unlike manifest.json, which describes what dbt intends to build, catalog.json reflects what actually exists in the warehouse after a build โ€” real column types, real row counts where the adapter supports it.

Terminal window
dbt docs generate
cat target/catalog.json | jq '.nodes["model.my_project.stg_orders"].columns'

This distinction matters when debugging a mismatch between what a modelโ€™s YAML claims about a column and what actually landed in the warehouse โ€” catalog.json is ground truth, manifest.json is intent.


Using Artifacts to Build Custom Tooling

Because artifacts are just JSON, teams commonly build lightweight internal tools directly against them rather than waiting for a feature to appear in dbt itself.

import json
with open("target/manifest.json") as f:
manifest = json.load(f)
# Find every model with zero tests attached โ€” a data quality audit
untested_models = [
node_id for node_id, node in manifest["nodes"].items()
if node["resource_type"] == "model" and len(node.get("columns", {})) > 0
and not any(manifest["nodes"].get(f"test.{node_id.split('.', 2)[2]}"))
]

This kind of script โ€” scanning manifest.json for models lacking test coverage, then surfacing that as a report โ€” is a common internal tool teams build to enforce testing standards without manually auditing every model file.


Comparing Artifacts Across Runs

Since artifacts are timestamped JSON snapshots, comparing two runsโ€™ run_results.json files is a straightforward way to detect regressions โ€” a model that used to take 2 seconds now taking 45 is visible immediately as a diff between two artifact files, without needing a dedicated performance monitoring tool.

Terminal window
# Simplified comparison concept
diff <(jq '.results[] | .execution_time' old_run_results.json) \
<(jq '.results[] | .execution_time' new_run_results.json)

Artifact Schema Versioning

Artifacts include a dbt_schema_version field, and the schema does change between dbt versions โ€” any tooling built against these files should check the schema version rather than assuming a fixed structure indefinitely, since a dbt upgrade can restructure fields in ways that silently break a script relying on an undocumented, version-specific shape.

{
"metadata": {
"dbt_schema_version": "https://schemas.getdbt.com/dbt/manifest/v12.json"
}
}

sources.json in Detail

Worth a closer look on its own: sources.json is produced specifically by dbt source freshness and captures, per source table, the exact freshness status along with the timestamps used to compute it.

{
"results": [
{
"unique_id": "source.my_project.raw.orders",
"status": "pass",
"max_loaded_at": "2026-07-04T08:12:00Z",
"snapshotted_at": "2026-07-04T09:00:00Z",
"max_loaded_at_time_ago_in_s": 2880
}
]
}

This structure is what makes programmatic freshness-gated orchestration possible, as covered in Artifacts in Orchestration โ€” a script can check max_loaded_at_time_ago_in_s directly rather than re-implementing freshness logic independently of what dbt already computed.

Common Mistakes

Parsing artifacts without checking the schema version. A script that worked fine against dbt 1.5โ€™s manifest structure can silently break after upgrading to a newer version with restructured fields.

Committing target/ to version control. Artifacts are generated output, not source โ€” they belong in .gitignore, regenerated fresh by CI/CD rather than tracked as committed files.

Building custom tooling against run_results.json without also checking manifest.json. Execution results alone donโ€™t tell you why something ran or how it depends on other nodes โ€” the two artifacts are usually needed together for anything beyond simple pass/fail reporting.

Summary

ArtifactAnswers
manifest.jsonWhat does this project look like, structurally?
run_results.jsonWhat actually happened during the last run?
catalog.jsonWhat does the warehouse actually contain right now?
sources.jsonAre sources fresh enough to trust?

dbtโ€™s JSON artifacts are what make the projectโ€™s internals programmatically accessible โ€” the foundation for orchestration integration, custom monitoring, and any tooling that needs to understand a dbt project without re-parsing its SQL from scratch.