Model contracts
Related documentation
Why define a contract?
Defining a dbt model is as easy as writing a SQL select statement. Your query naturally produces a dataset with columns of names and types based on the columns you select and the transformations you apply.
While this is ideal for quick and iterative development, for some models, constantly changing the shape of its returned dataset poses a risk when other people and processes are querying that model. It's better to define a set of upfront "guarantees" that define the shape of your model. We call this set of guarantees a "contract." While building your model, dbt will verify that your model's transformation will produce a dataset matching up with its contract, or it will fail to build.
Considerations
There are some considerations to keep in mind when using model governance features:
-
Model governance features like model access, contracts, and versions strengthen trust and stability in your dbt project. Because they add structure, they can make rollbacks harder (for example, removing model access) and increase maintenance if adopted too early. Before adding governance features, consider whether your dbt project is ready to benefit from them. Introducing governance while models are still changing can complicate future changes.
-
Governance features are model-specific. They don't apply to other resource types, including snapshots, seeds, or sources. This is because these objects can change structure over time (for example, snapshots capture evolving historical data) and aren't suited to guarantees like contracts, access, or versioning.
Prerequisites
These places support model contracts:
dbt_project.ymlfileproperties.ymlfile- SQL models
- Models materialized as one of the following:
tableview— views offer support for column names and data types, but notconstraintsincremental— withon_schema_change: append_new_columnsoron_schema_change: fail
- Certain data platforms, but the supported and enforced
constraintsvary by platform
These places do NOT support model contracts:
- Python models
materialized vieworephemeral— materialized SQL models- Custom materializations (unless added by the author)
- Models with recursive CTE's in BigQuery
- Other resource types, such as
sources,seeds,snapshots, and so on
Define a contract
Let's say you have a model with a query like:
-- lots of SQL
final as (
select
customer_id,
customer_name,
-- ... many more ...
from ...
)
select * from final
To enforce a model's contract, set enforced: true under the contract configuration.
When enforced, your contract must include every column's name and data_type (where data_type matches one that your data platform understands).
If your model is materialized as table or incremental, and depending on your data platform, you may optionally specify additional constraints, such as not_null (containing zero null values).
models:
- name: dim_customers
config:
contract:
enforced: true
columns:
- name: customer_id
data_type: int
constraints:
- type: not_null
- name: customer_name
data_type: string
...
When building a model with a defined contract, dbt will do two things differently:
- dbt will run a "preflight" check to ensure that the model's query will return a set of columns with names and data types matching the ones you have defined. This check is agnostic to the order of columns specified in your model (SQL) or YAML spec.
- dbt will include the column names, data types, and constraints in the DDL statements it submits to the data platform, which will be enforced while building or updating the model's table, and order the columns per the contract instead of your dbt model.
Platform constraint support
Select the adapter-specific tab for more information on constraint support across platforms. Constraints fall into three categories based on definability and platform enforcement:
- Definable and enforced — The model won't build if it violates the constraint.
- Definable and not enforced — The platform supports specifying the type of constraint, but a model can still build even if building the model violates the constraint. This constraint exists for metadata purposes only. This approach is more typical in cloud data warehouses than in transactional databases, where strict rule enforcement is more common.
- Not definable and not enforced — You can't specify the type of constraint for the platform.
- Redshift
- Snowflake
- BigQuery
- Postgres
- Spark
- Databricks
- Fabric
- Athena
| Constraint type | Definable | Enforced |
|---|---|---|
| not_null | ✅ | ✅ |
| primary_key | ✅ | ❌ |
| foreign_key | ✅ | ❌ |
| unique | ✅ | ❌ |
| check | ❌ | ❌ |
| Constraint type | Definable | Enforced |
|---|---|---|
| not_null | ✅ | ✅ |
| primary_key | ✅ | ❌ |
| foreign_key | ✅ | ❌ |
| unique | ✅ | ❌ |
| check | ❌ | ❌ |
| Constraint type | Definable | Enforced |
|---|---|---|
| not_null | ✅ | ✅ |
| primary_key | ✅ | ❌ |
| foreign_key | ✅ | ❌ |
| unique | ❌ | ❌ |
| check | ❌ | ❌ |
| Constraint type | Definable | Enforced |
|---|---|---|
| not_null | ✅ | ✅ |
| primary_key | ✅ | ✅ |
| foreign_key | ✅ | ✅ |
| unique | ✅ | ✅ |
| check | ✅ | ✅ |
Currently, not_null and check constraints are enforced only after a model is built. Because of this platform limitation, dbt considers these constraints definable but not enforced, which means they're not part of the model contract since they can't be enforced at build time. This table will change as the features evolve.
| Constraint type | Definable | Enforced |
|---|---|---|
| not_null | ✅ | ❌ |
| primary_key | ✅ | ❌ |
| foreign_key | ✅ | ❌ |
| unique | ✅ | ❌ |
| check | ✅ | ❌ |
Currently, not_null and check constraints are enforced only after a model is built. Because of this platform limitation, dbt considers these constraints definable but not enforced, which means they're not part of the model contract since they can't be enforced at build time. This table will change as the features evolve.
| Constraint type | Definable | Enforced |
|---|---|---|
| not_null | ✅ | ✅ |
| primary_key | ✅ | ❌ |
| foreign_key | ✅ | ❌ |
| unique | ❌ | ❌ |
| check | ✅ | ✅ |
Microsoft Fabric creates the table first, then adds some constraints in separate steps. Only not_null is checked when the model builds. Primary key, foreign key, and unique constraints are recorded on the table for reference, but Fabric does not check them when new data is added. Define primary_key, foreign_key, and unique at the model level. The adapter ignores those constraint types at the column level.
| Constraint type | Definable | Enforced |
|---|---|---|
| not_null | ✅ | ✅ |
| primary_key | ✅ | ❌ |
| foreign_key | ✅ | ❌ |
| unique | ✅ | ❌ |
| check | ❌ | ❌ |
| Constraint type | Definable | Enforced |
|---|---|---|
| not_null | ❌ | ❌ |
| primary_key | ❌ | ❌ |
| foreign_key | ❌ | ❌ |
| unique | ❌ | ❌ |
| check | ❌ | ❌ |
FAQs
Was this page helpful?
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.