What you will be able to do
- Explain which governance features dbt offers and which resource types they apply to
- Define a group with an owner and assign models to it at project, model or file level
- Choose between private, protected and public access and predict the error a disallowed ref raises
- Recognise where access rules do not reach: warehouse grants, run-operation and installed packages
Key concept
Model governance — A set of model-level controls in dbt (access, contracts, versions and namespaces) that let you treat a model as a stable interface. They decide who may ref the model, what shape it is guaranteed to have, and how it can change without breaking its consumers.
1.What model governance controls
Once other teams build on your models, those models become interfaces, and model governance is how dbt lets you manage them that way. It answers three questions about a model: who is allowed to ref it, what shape it is guaranteed to have, and how it may change over time. Each question has its own feature. All of them work in dbt v1 and on the dbt platform except project dependencies, which need an Enterprise-tier plan.
| Feature | What it controls | Availability |
|---|---|---|
| Model access | Whether a model is public or private, and so who can ref it | dbt v1 and dbt platform |
| Model contracts | Column names, data types and constraints, checked before the model builds | dbt v1 and dbt platform |
| Model versions | An upgrade pathway and deprecation window when a breaking change is unavoidable | dbt v1 and dbt platform |
| Model namespaces | Groups and packages that mark ownership boundaries | dbt v1 and dbt platform |
| Project dependencies | Resolving refs to public models in other projects | dbt Enterprise-tier plans only |
Cross-project references are built on access. A downstream project can only reach the models its producer has marked access: public, and that marker is the intentional interface the maintainer has designated. Wiring up the dependency itself is covered under [setting up additional projects](lesson:7.1). This lesson is about the governance that decides what the dependency is allowed to see.
Governance has a cost, so adopt it on purpose. Access, contracts and versions add structure, and structure makes some rollbacks harder: once consumers depend on a public model, removing that access breaks them. If you add governance while models are still being reshaped every week, every reshape turns into a negotiation. Put it on models that have settled.
Governance applies only to models. Snapshots, seeds and sources cannot be made public, contracted or versioned. Their structure can change over time (a snapshot captures evolving history, for example), so they don't suit those guarantees.
Checkpoint 1 of 6· Check yourself
A team wants to publish four resources to a downstream project with access: public. Which one can actually carry that setting?
Access, contracts and versions are model-only features. Seeds, snapshots and sources can change structure over time, so dbt doesn't offer these guarantees for them.
“Governance features are model-specific. They don't apply to other resource types, including snapshots, seeds, or sources.”Source: docs.getdbt.com
2.Groups define ownership boundaries
A group is a named collection of nodes in the DAG, and every group has an owner. Groups do two things. They turn an implicit relationship, such as "these are the finance models", into an explicit one with someone responsible for it. They also give private access its meaning: a private model can be referenced only from inside its own group. Thinking about the interfaces between groups tends to produce a less tangled DAG, and those interfaces are natural candidates for the seams between separate projects later on.
Groups are declared in YAML under a groups: key. Each one needs a name that is unique in the project and an owner with at least a name: or email:. From v1.10 a group can also take a description and config.meta, for things like a cost centre or a data classification. To keep the definitions in one place, use a single _groups.yml at the root of the models directory. Alternatively, put it at the root of a groups directory and add that directory to model-paths.
You can assign membership at three levels: in dbt_project.yml for a whole folder, in a model's YAML, or in a config() block inside the SQL file. Most well-structured projects already use subdirectories, so the project-level form usually does most of the work:
models:
my_project_name:
marts:
customers:
+group: customer_success
finance:
+group: financeMembership is strict. A node belongs to at most one group, and groups cannot be nested. Models, tests, seeds, snapshots, analyses and metrics can be members. Sources and exposures cannot. A group set in a model's YAML or in-file config overrides the folder-level assignment, so one model can be moved out of its folder's group without restructuring the directory.
Checkpoint 2 of 6· Check yourself
dbt_project.yml applies +group: finance to models/marts/finance/. One model in that folder sets group: marketing in its YAML. What is its group membership?
A model belongs to exactly one group. When its YAML or in-file config sets a different group, that setting overrides the one applied at the project level.
“If you set a different group in that model's YAML or in-file config, it will override the group applied at the project level.”Source: docs.getdbt.com
3.Private, protected and public access
The access modifier sets how far a model's ref reaches. There are three levels, and each one widens the audience of the one before it.
| Access | Referenceable by | Use it for |
|---|---|---|
| private | Same group | Intermediate transformations that are implementation details |
| protected (default) | Same project, or the project installed as a package | Models useful elsewhere in this project but not meant to be exposed outside it |
| public | Any group, package, or project | Stable, mature interfaces for other teams and projects |
Protected is the default so that existing projects don't break when groups are introduced, because their references may already cross the new boundaries. For new models, set private instead. That stops other parts of the project from depending on a model nobody designed for sharing. Promote a model to public when it really is a product, and rerun a production job afterwards so the change takes effect.
# First, define the group and owner
groups:
- name: customer_success
owner:
name: Customer Success Team
email: cx@jaffle.shop
# Then, add 'group' + 'access' modifier to specific models
models:
# This is a public model -- it's a stable & mature interface for other teams/projects
- name: dim_customers
config:
group: customer_success # changed to config in v1.10
access: public # changed to config in v1.10
# This is a private model -- it's an intermediate transformation intended for use in this context *only*
- name: int_customer_history_rollup
config:
group: customer_success # changed to config in v1.10
access: private # changed to config in v1.10dbt enforces access at parse time. If marketing_model in the marketing group refs a model that is private to finance, the run stops with a DbtReferenceError saying the referenced node is private to the finance group. No SQL reaches the warehouse. There is one more restriction to know: a model with materialized='ephemeral' cannot be public, and dbt parse rejects that combination as an invalid value for the access field.
Checkpoint 3 of 6· Fill the gap
marketing_model (group marketing) refs this model and fails with a DbtReferenceError. Which access value produces that error?
models:
- name: finance_private_model
config:
access: ? # changed to config in v1.10
group: financeOnly private access confines references to the model's own group. Protected would allow any group in the same project, and public would allow any project.
Source: docs.getdbt.comCheckpoint 4 of 6· Exam question
A dbt Cloud admin defines `finance_model` with `access: private` inside the `finance` group. A model called `marketing_model`, which is not a member of the `finance` group, calls `ref('finance_model')` in the same project. What happens when the project is parsed?
Correct answer: A — dbt raises a DbtReferenceError during parsing and blocks the run, because finance_model is private to the finance group and cannot be referenced from outside it.
- A. Private access scopes a model to members of its own group, so any ref from a model outside that group fails parsing with a DbtReferenceError. This is the mechanism dbt uses to enforce ownership boundaries between teams.
- B. There is no aliasing or rerouting behavior tied to access levels; dbt does not silently substitute a different model when a reference is disallowed. An access violation surfaces as a parsing error, not a swapped dependency.
- C. Access enforcement is independent of contracts. Private access blocks out-of-group references whether or not the model has an enforced contract, so the absence of a contract does not change the outcome.
- D. Group and access rules apply during parsing regardless of environment, and a violation is a hard error rather than a warning. dbt does not defer this enforcement to production-only checks.
4.Where access rules stop
Access controls who may build on a model in dbt. It does not control who can query the data. Setting public signals that other teams may take a dependency, on the assumption that they have asked for and been granted select on the underlying dataset. You grant that separately. Typical moves are to use the grants config to open public models to downstream queriers, and to restrict private ones by revoking default grants or landing them in a different schema.
Inside the dbt account, access lines up with development permissions. Anyone who can develop in a project can see and change every model in it, private ones included. A user in the same account who can't develop there can't see its private models and can depend only on its public ones.
There are two places where dbt does not enforce access at all, and both catch people out:
- **dbt run-operation.** A macro invoked this way can ref() any model, private and protected included, because group and access controls are not enforced.
- Package dependencies. When another project is installed as a package, its source code becomes part of your runtime. Access restrictions are off by default, so protected models can be referenced as if they were your own, and private ones too when your model shares their group.
A package maintainer can close that gap from their own dbt_project.yml. With the flag below set, any ref from outside the package to a protected or private model fails, and only public models can be reached. Project dependencies on dbt Enterprise behave differently: they resolve refs through a metadata service, and only public models are reachable.
restrict-access: True # default is FalseCheckpoint 5 of 6· Check yourself
Your project installs jaffle_finance as a package, and your model refs one of its protected models. The package's dbt_project.yml doesn't mention restrict-access. What happens?
restrict-access defaults to False, so protected package models can be referenced as if they were defined in the root project. Only restrict-access: True limits external refs to public models.
“model access restrictions are "off" by default for models defined in packages”Source: docs.getdbt.com
Checkpoint 6 of 6· Exam question
Team B adds Team A's project `core_finance` to `dependencies.yml` and calls `{{ ref('core_finance', 'monthly_revenue') }}`. The build fails to resolve the reference. Team A confirms `monthly_revenue` already has `access: public` set. What is the most likely remaining cause of the failure?
Correct answer: A — The core_finance project has never completed a successful job run in its designated deployment environment, so no manifest metadata has been published for Team B to resolve against.
- A. Cross-project resolution depends on manifest metadata published by a successful job run in the producer project's designated deployment environment. Without that run, the metadata service has nothing to resolve the reference against, even though the model is correctly marked public.
- B. An enforced contract is a recommended companion to public access, not a hard requirement for cross-project resolution. A public model without a contract can still be referenced across projects.
- C. The project name in dependencies.yml must exactly match the name declared in the upstream project's dbt_project.yml, so a mismatch there is a real failure mode, and license tier is not the described symptom here.
- D. A version argument is only required when referencing a specific pinned version of a versioned model. Unversioned public models resolve correctly with the standard two-argument ref.
Sources4
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Models with no access setting are private to their group.Why is that wrong?
The default is protected, so any model in the same project can ref them. Set private explicitly on new models that shouldn't be shared.
Covered in Private, protected and public access
2.Marking a model access: public grants downstream users select on it in the warehouse.Why is that wrong?
Access only governs who may ref the model in dbt. Warehouse permissions are managed separately, for example with the grants config.
Covered in Where access rules stop
3.Any model, including an ephemeral one, can be published as public.Why is that wrong?
Ephemeral models cannot have public access. dbt parse fails with an invalid value error for the access field.
Covered in Private, protected and public access
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“except project dependencies, which is only available to dbt Enterprise-tier plans”
↩︎ What model governance controls“Introducing governance while models are still changing can complicate future changes.”
↩︎ What model governance controls“dbt supports model governance to help you control who can access models, what data they contain, how they change over time”
↩︎ Key concept“Governance features are model-specific. They don't apply to other resource types, including snapshots, seeds, or sources.”
↩︎ Checkpoint - 2.
“You're using an intentional interface designated by the model's maintainer with access: public.”
↩︎ What model governance controls - 3.https://docs.getdbt.com/docs/build/groupsOfficial docs
“Group members may include models, tests, seeds, snapshots, analyses, and metrics. (Not included: sources and exposures.)”
↩︎ Groups define ownership boundaries“If a grouped model's access property is set to private, only resources within its group can reference it.”
↩︎ Private, protected and public access - 4.
“Each model can only belong to one group, and groups cannot be nested.”
↩︎ Groups define ownership boundaries“Any group, package, or project. When defined, rerun a production job to apply the change”
↩︎ Private, protected and public access“When a macro is invoked with dbt run-operation, dbt doesn't enforce model access or group controls”
↩︎ Where access rules stop“Users in the same dbt account without access to develop in a project cannot view that project's private models”
↩︎ Where access rules stop“Only models with access: public can be referenced outside the package.”
↩︎ Where access rules stop“it is recommended to set the access modifier of a new model to private”
↩︎ Exam trap 1“does not trigger dbt to automagically grant select on that model to every user or role in your data platform”
↩︎ Exam trap 2“Models with materialized set to ephemeral cannot have the access property set to public.”
↩︎ Exam trap 3“If you set a different group in that model's YAML or in-file config, it will override the group applied at the project level.”
↩︎ Checkpoint“By default, all models are protected.”
↩︎ Prediction“does not trigger dbt to automagically grant select on that model to every user or role in your data platform”
↩︎ Prediction“model access restrictions are "off" by default for models defined in packages”
↩︎ Checkpoint