Data Contracts in dbt: How to Stop Silent Pipeline Failures
· dbt, data contract, data quality
In short: a data contract declares the shape a table promises to deliver — columns, types
and constraints. You attach it to the model in dbt (contract: enforced: true). When the data
drifts from the agreement, the build fails immediately, before the bad shape reaches a report.
It trades a silent failure for a loud, early one. That's the whole
point.
What is a data contract?
It's an explicit agreement about a table's shape: which columns exist, what type each is, what may be null, and what must be unique. Without a contract, every consumer guesses the shape and hopes it never changes. With one, the shape is law. Whatever breaks that law is stopped at the door, not discovered at close.
It targets the most dangerous failure of all: the pipeline that completes with no error and
delivers the wrong number. A source changes a type, autodetect decides on its own, a column
disappears. The build still runs green, because nothing told it what "correct" looked like.
How dbt enforces it (model contracts)
dbt has native model contracts.
You declare columns and types in YAML and enable the contract. At materialization time, dbt checks
the structure of your SELECT against what you promised. A type doesn't match, or a contracted
column is missing? It stops the build with a clear error instead of writing a malformed table.
models:
- name: fct_transactions
config:
contract:
enforced: true
columns:
- name: transaction_id
data_type: string
constraints:
- type: not_null
- name: amount
data_type: numeric # money is numeric, never float
constraints:
- type: not_null
- name: created_at
data_type: timestamp
Note amount as numeric: the contract is also where you pin the money type and stop
autodetect from turning it into a float and shipping rounding errors downstream.
One precision that saves frustration: the contract always enforces the shape — column names
and types — and the build fails if they diverge. Constraints like not_null, though, depend on
your warehouse. Postgres enforces them all; Snowflake only not_null; BigQuery and Databricks
define but don't enforce them (they stay as metadata). Lock the shape with the contract; for value
rules, pair it with tests.
Contract-first: the shape before the data
The bigger win is inverting the order. Write the contract first, then build the model to satisfy it. The shape of the layer your BI consumes (the Gold) becomes a design decision, not an accident of whatever the source happened to send today. The report now depends on a stable, auditable shape, and any upstream change hits the contract before it can contaminate the output. The order matters.
In a fraud pipeline I built, the Gold layer went 100% contracted. When a source changed a type, the
build started failing on the spot instead of becoming a wrong report at month-end. And this is no
longer a fad: there's an open standard — the
ODCS (Open Data Contract Standard), now
under the Linux Foundation — and money moving in, with Gable raising a
$20M Series A
for the shift-left movement. dbt itself is following, with the Rust-based
Fusion engine and its column-level lineage.
What about when the schema really does change?
A contract stops what breaks the shape at materialization. On its own, though, it won't tell you the source drifted. Pair it with two things:
- Schema-change monitoring at the entry, so you learn a source changed the moment it happens.
- A quarantine for rows that violate the rules — held and flagged rather than silently dropped or passed through.
Contract (shape is law), monitor (the source drifted) and quarantine (bad rows stay visible) are the three layers of a pipeline that fails loud, not silent.
Frequently asked questions
Do I need to rewrite my pipeline to use data contracts?
No. A contract sits on top of a model you already have. You start with one table — usually the Gold model your most important report reads from — declare its shape, and turn enforcement on. Nothing upstream changes. You expand from there, one model at a time, at whatever pace you want.
Do contracts replace dbt tests?
They don't. They cover different failures, and you want both. A contract guards the structure — columns, types, the presence of a field — and fails the build before a malformed table is even written. A test checks the content — this column is unique, this value falls in range, this foreign key resolves — after the table exists. Structure first, content second.
Does this work if I don't use dbt?
The dbt syntax here is specific, yes. The idea is not. A data contract is just an enforced, versioned agreement about a table's shape, and you can implement one in plain SQL checks, Great Expectations, or your warehouse's own constraints. dbt makes it a one-line config. Without dbt, it's more wiring — but the principle, and the payoff, are identical.
I build all three layers into every ETL/ELT project. If your pipeline "runs fine" but the number is sometimes wrong, the contract is the first place I look.
This is exactly what I do in A data pipeline that breaks loud and early — never in silence.
Read next
Request a diagnostic of your close
Leave your contact and I'll get back with a quick read of where your reporting depends on one person — no commitment.
A human replyNo charge until the proposalMutual NDA