There is a class of data failure that never triggers an alarm. The pipeline runs to the end, the report refreshes at its usual time, the numbers appear on the dashboard — and they are wrong. Upstream, someone on another team renamed a column, changed a date format, or started sending values in cents instead of euros. Nobody warned anyone, because nobody knew that field had consumers on the other side.
This is the silent problem of modern data engineering. Data crosses dozens of systems and teams, but the agreements about what each field means live, at best, in the head of whoever built the pipeline. When that person moves to another project, or when volume grows, the cost shows up — in reworked reports, delayed decisions, and lost trust in the data team.
A data contract is the answer to this problem. It is an explicit, versioned, verifiable agreement between those who produce data and those who consume it, describing the structure, the meaning, and the quality guarantees of what is shared. This article explains what it is, what it should contain, and how to introduce it without turning everything into bureaucracy that nobody follows.
What a data contract is
A data contract formalises a promise. The team that produces a dataset commits to delivering it with a given shape and quality, and to giving notice before changing that promise. It is not a document forgotten in a wiki: it is a machine-readable specification, stored next to the code and checked automatically whenever the data changes.

The best analogy is an API. When a team exposes an API, it publishes a contract — the fields, the types, what is required — and consumers build on top of that promise, trusting it will not change overnight. For years, analytical data had no such discipline: a table in a data warehouse was treated as an internal detail that anyone could alter. The data contract brings to the analytical world the same rigour that software engineering has long applied to its interfaces.
The problem: data that breaks silently
To grasp the value of a contract, you have to look at how things break without one. The most common failure is an upstream schema change: a column that disappears, changes type, or starts allowing nulls. The pipeline may not fail immediately — it often keeps running and writing data, only wrong.
Then there are changes in meaning, even more treacherous because the schema does not even change. The estado_encomenda field starts including a new value that no report anticipated. The currency of valor_total switches from euros to dollars in one region. The definition of an active customer is changed at the source without the analytics team knowing. The type is still correct; the number stops meaning what it meant.
The common denominator is the absence of an explicit agreement. Those who produce the data do not know who consumes it, or with what expectations. Those who consume it have no way to know the promise has changed. A contract makes that agreement visible and, above all, verifiable.
What a good data contract includes
A useful contract goes well beyond the list of columns. It should describe, in a way readable by both machines and people, the following elements:
- Schema and types. The fields, their types, which are required and which allow nulls. It is the foundation, but only the start.
- Semantics. What each field means in business language: units, currency, time zone, the granularity of each row, and the allowed values in categorical fields.
- Quality guarantees. Rules the data promises to meet — key uniqueness, valid ranges, a maximum percentage of nulls, referential integrity.
- SLA and freshness. How often the data is updated, the expected latency, and what happens when a load fails or arrives late.
- Ownership. Who is responsible for the dataset, how to reach that team, and where changes are announced.
- Version. A version number that lets the contract evolve in a controlled way, distinguishing compatible changes from changes that break consumers.
In practice, this usually lives in a structured file — YAML or JSON — stored in the repository next to the pipeline that produces the data. The format matters less than the principle: the contract is code, reviewed as code and tested as code.
Contract, catalog and governance: what is different
It is easy to confuse data contracts with neighbouring initiatives, but the focus is distinct. A data catalog describes what exists — it helps you discover tables and understand their content. A contract goes further: it guarantees that what exists behaves as promised, and fails the build when it does not.
Data governance, in turn, defines organisation-level policies: who can access what, how sensitive information is classified, which retention rules apply. The contract is the concrete mechanism that operationalises part of that governance at the point where data changes hands. They do not compete — they complete each other. The catalog makes data discoverable, governance sets the rules, and the contract makes the promises verifiable day to day.
How to implement it in practice
What separates a data contract from a good intention is automatic verification. Without enforcement, a contract is just documentation — and documentation always drifts from reality. The central idea is simple: whenever the producer tries to publish data that violates the contract, the process fails before that data reaches consumers.
This usually materialises at two points. First, in the producer's CI/CD: a change to the pipeline that breaks the declared schema does not pass the tests and is not merged. Second, at runtime: each load is validated against the contract and, if a quality rule fails, the data is held in quarantine rather than propagated. Data testing and schema validation tools make this accessible without building everything from scratch, and transformation platforms such as dbt let you declare and test expectations right next to the transformations themselves.
The essential point is not the tool, it is the shift in responsibility: the producer can no longer change the data silently. The promise now has consequences at the moment it is broken, not weeks later, in a wrong report.
Evolving the contract without breaking consumers
A contract is not a cage. Data evolves, and the contract has to evolve with it — the challenge is doing so without catching consumers by surprise. The decisive distinction is between compatible changes and breaking changes.
Adding a new column is, almost always, a compatible change: whoever already consumed the data keeps working. Removing a column, changing its type, or altering the meaning of a value is a breaking change — and it demands a process. That process usually means versioning the contract, announcing the new version in advance, keeping the old version through a transition period, and only then retiring it. It is exactly what a software team does when it deprecates an API version.
Handling evolution with this discipline transforms the relationship between teams. Instead of unilateral changes followed by firefighting, there is a predictable channel: producers know they can evolve, and consumers know they will get notice and time to adapt.
Mini case: a retail company
Consider a retail company with physical and online stores, whose data department fed around forty reports from tables shared by the e-commerce team. In an update to the store platform, the field indicating the order value started including VAT, when before it excluded it. Nothing in the schema changed — the type was still decimal — so no technical test flagged the problem.
For eleven days, revenue reports overstated sales by around 23%, and stock replenishment decisions were made on those numbers. When the discrepancy was finally detected, correcting the reports and rebuilding the affected history took almost two weeks of work from two people.
The response was not to buy an expensive tool, but to introduce a contract for the three most critical e-commerce tables. The contract explicitly declared that valor_encomenda excluded VAT and included a per-item range check. Months later, a similar change was attempted upstream again — this time the build failed the same day, with a clear message, and the team fixed it before any report was affected. The cost of the contract was a few days of work; the cost avoided, recurring weeks.
Common mistakes and where to start
The fastest way to make a data contract initiative fail is to try to contract everything at once. That produces hundreds of files nobody maintains and a sense of bureaucracy that pushes teams away. A few recurring mistakes, and how to avoid them:
- Starting from the tool, not the pain. First identify the datasets whose breakage causes the most damage, and start with those.
- Contracts without enforcement. A contract that does not fail the build is just documentation that will drift from reality. Automatic checking is what gives it value.
- Forgetting semantics. Validating types is easy; what breaks reports are changes in meaning. Document units, currency, and business definitions.
- Not defining an owner. Without clear ownership, nobody maintains the contract or responds when it fails.
So, where to start: pick one or two critical datasets, write a minimal contract covering schema, semantics, and two or three quality rules, and wire it to automatic checking. Prove the value with one prevented breakage and let adoption grow from there.
In practice
Data contracts are not a new technology to install, but a discipline to adopt: making explicit, versioned, and verifiable the promises that already exist implicitly between teams. The return is not in impressing anyone with architecture, but in avoiding the class of failure that silently erodes trust in data.
Start small, at the point where it hurts most, with automatic checking from day one. A single contract that catches a break before it reaches a report often pays, on its own, for the entire effort of creating it. From there, the question stops being who touched this data and becomes which version of the contract we want for next quarter.