Skip to content

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.

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

Does this sound like your problem?

A 30-minute call, no commitment. I'll tell you where the risk is and what to fix first.