What you will be able to do
- Decide where to split a dbt DAG into separate projects and whether to use one repository or several
- Create an additional dbt project with its own connection, repository and deployment environment
- Move a group of models out of a monolith into a new project, or migrate from package- or source-based coordination
- Declare a project dependency in dependencies.yml and reference upstream public models with a two-argument ref
- Set up two projects that depend on each other in the right order
Key concept
Project dependency — A project dependency is an entry in dependencies.yml that lets one dbt project ref another project's public models without importing its code. A metadata service resolves each reference against what the upstream project has already built, so the downstream project never parses or runs the upstream models.
1.When a second project is worth it, and where to cut
Add a second dbt project when a single project is causing problems for the organisation, not as a starting setup. The usual signs are these. The model count is slowing development. Teams' workflows have drifted apart and need to be separated. Data products are becoming less reliable because teams struggle to communicate. Security or governance requirements would benefit from isolation. A team just starting with dbt does not need any of this yet. The features behind a multi-project setup work on their own and can be adopted as the project grows.
Cross-project references are the feature that turns several projects into one mesh. They are available only on the dbt Enterprise and Enterprise+ plans. On any lower plan you can still have separate projects, but a ref cannot reach from one to another.
The hardest decision is where to draw the line in the DAG. A vertical split separates layers of transformation. Examples are a shared staging project that others build on but cannot edit, an early project that masks PII before anyone downstream sees it, and an isolated set of expensive models that can't be selected by accident. A horizontal split separates by domain or source. Examples are the marketing team's data flow, clickstream events kept apart from transactional data, and two teams that work at different speeds. You can combine the two kinds, but pick one to do first. In a hub-and-spoke organisation, break out the central platform project before splitting the rest into domains.
Good boundaries usually exist already. Look at which models your jobs build together, how the lineage graph clusters, and which resource groups people have defined in selectors.yml. A team that sits entirely downstream of the current project is often the easiest first consumer.
| Layout | Fits when | Watch for |
|---|---|---|
| Monorepo (all projects in one repository) | A smaller team mainly wants faster, simpler development | Becomes unwieldy as projects, models and contributors grow |
| Multi-repo (one repository per project) | A larger organisation must separate projects for security and for teams with different development styles and rhythms | Changes across projects are harder, and should be treated like an API change |
Since Mesh launched, the most common pattern has been one project per team, each in its own repository. Some contributions across projects are healthy. If teammates need a coordinated change across several projects every week, or for a fifth or more of their work, the boundaries are in the wrong place.
Checkpoint 1 of 8· Check yourself
A security review requires that raw customer PII is masked before any analytics team can model it. Which split addresses this most directly?
Vertical splits separate transformation layers in DAG order. Isolating early models to mask PII is a named use case for them.
“Isolating earlier models for security and governance requirements to separate out and mask PII data so that downstream consumers can't access it”Source: docs.getdbt.com
Checkpoint 2 of 8· Exam question
The `analytics` project's `dependencies.yml` lists `finance` under a `projects:` block, and a model calls `{{ ref('finance', 'stg_orders') }}`. Every job in `analytics` fails to resolve that ref, even though `stg_orders` in the `finance` project is set to `access: public`. What is the most likely cause?
Correct answer: C — The `finance` project's designated production environment has not completed a successful run, so no manifest metadata has been published for the ref to resolve against.
- A. Entries under a `projects:` block in `dependencies.yml` reference an upstream project by name only; they do not take a `version:` pin the way `packages:` entries do, so this is not a valid failure mode.
- B. Project dependencies never pull down or compile the upstream project's source code at all, so no plan tier restriction on importing compiled package source could be the cause of this failure.
- C. This is correct. A cross-project ref resolves against metadata published by the upstream project's designated deployment environment, and that metadata is only published after at least one successful job run there; without it, there is nothing for the metadata service to serve.
- D. Group membership on its own does not override or revoke an explicit `access: public` setting; groups govern ownership and the `private` access level, not whether a public model can be referenced.
2.Creating the new project in dbt
Every project in a mesh is a complete dbt project with its own data platform connection, git repository and environments. To create one, go to Account settings, then Projects, and click New project. Enter a name, pick the data platform and fill in the connection settings, then click Test Connection before continuing. As a running example, take a fictional company that creates two projects this way: an upstream "Jaffle | Data Analytics" and a downstream "Jaffle | Finance", with the default databases jaffle_da and jaffle_finance. Connection testing has its own lesson: [testing a project connection](lesson:1.3).
A project can join a mesh only once it has a deployment environment set as Production or Staging. Each project can have one deployment environment designated as Production. To create it, go to Deploy → Environments → Create New Environment, choose Deployment as the type, then select Production under Set deployment type. Which environment type cross-project references resolve against is covered in [environment types and cross-project references](lesson:7.2).
Create a new project for a new codebase, never for another stage of the same codebase. Project names must be unique within a dbt account, and cross-project references find projects by name. Differences between dev and prod belong in environments. Each environment can use a different connection, connections can be reused across projects and environments, and one project can use several connections of the same warehouse type. That removes the usual reason teams create a duplicate project.
Two things must be true before a downstream team can build on a new foundational project. At least one model must be defined as public, and a deployment job must have run successfully. That run produces the manifest.json file that downstream projects resolve their references against.
Checkpoint 3 of 8· Check yourself
A foundational project has public models and a working development environment, but nothing has ever run in deployment. A downstream team adds it as a dependency. What is missing?
Downstream references resolve from the manifest.json produced by a successful deployment job in the upstream Production environment.
“Define a Production deployment environment in the upstream project and make sure at least one deployment job has run successfully there.”Source: docs.getdbt.com
Checkpoint 4 of 8· Exam question
A developer works in the `analytics` project and needs to build a marts model that reads from `stg_orders`, a public model owned by the upstream `finance` project (the `name` set in `finance`'s `dbt_project.yml`). Which line correctly authors that cross-project reference inside the `analytics` model's SQL?
Correct answer: B — `{{ ref('finance', 'stg_orders') }}`
- A. A single-argument `ref()` only looks for `stg_orders` within the current project's own DAG, so it cannot locate a model that lives in a different upstream project.
- B. This is correct. The two-argument form of `ref()` takes the upstream project's name as the first argument and the model name as the second, which is exactly how dbt resolves a cross-project reference through the metadata service.
- C. `source()` is reserved for referencing raw, unbuilt tables that dbt did not create, such as raw loaded data; a public model built by another dbt project is referenced with `ref()`, not `source()`.
- D. Passing a single dotted string as one argument is not valid `ref()` syntax; dbt expects the project name and model name as two separate arguments, not concatenated with a period.
3.Moving models into the new project
Before splitting anything, set up groups and access inside the monolith. Use groups to assign owners and set each model's access as tightly as current behaviour allows. Then run each group with selection syntax alongside your production jobs until you trust the boundaries. These settings are covered in [model governance](lesson:7.3). Once the boundaries are confirmed, splitting off one group follows a fixed checklist:
1. Move the group's models into a subfolder, along with their YAML entries and any parent or child models that belong with them.
2. Create a new dbt_project.yml in that subfolder.
3. Copy any macros the moved models use.
4. Create a new packages.yml there for the packages those models need.
5. Change every ref that crosses projects to two arguments, project name then model name. Set the upstream models to access: public, ideally with a model contract. A contract sets explicit expectations on the shape of the data, so changes upstream do not break the downstream consumers who rely on it.
6. Add a dependencies.yml to the downstream project that names the upstream project.
# in dependencies.yml
projects:
- name: jaffle_shopDiscipline matters more than speed here. Split one group at a time and aim for exact 1-to-1 parity with the old project. Note improvements for later instead of refactoring during the migration. Start inside the same repository, so git history gives you an easy way to undo the split. Circular references are still not allowed, and across projects the test is made per model: two projects may depend on each other in both directions, but a cycle exists when a model ends up depending on itself through models in either project. dbt checks for such cycles and raises an error.
Some organisations already run several projects. They either install the parent project as a dbt package or read its outputs with source(). Both approaches have costs. A package puts every upstream resource into the child's manifest, which slows dbt down. Sources break the lineage, because nothing connects the two projects. Moving either setup to the mesh is simpler than splitting a monolith. In the parent, mark the referenced models public and add contracts. In the child, add the parent to dependencies.yml. Then, if you used a package, remove its packages.yml entry and add the project name to each ref. If you used sources, replace each source() with a cross-project ref and delete the source definitions you no longer need.
Checkpoint 5 of 8· Check yourself
A finance project currently reads the platform team's tables with source() calls. What does moving it onto the mesh require in the finance project?
With the source method, the child project declares the dependency and replaces sources with two-argument refs. This restores the lineage that sources broke.
“Replace the {{ source() }} functions with cross project {{ ref() }} functions. Remove the unnecessary source definitions.”Source: docs.getdbt.com
4.Declaring the dependency and writing the ref
Packages and projects are two different kinds of dependency, and only project dependencies are part of the mesh. A package brings in its full source code, like a library. A project dependency brings in no code. The downstream project treats each upstream public model as an API that returns a dataset, and the model's maintainer is responsible for its quality.
| Aspect | Package dependency | Project dependency |
|---|---|---|
| What you get | The package's full source code, added to your project | References to the upstream project's public models, resolved by a metadata service |
| File | packages.yml or dependencies.yml | dependencies.yml only |
| Upstream models | Parsed and runnable in your project | Can be selected, but your dbt run or dbt build cannot build them |
| Upstream vars, env vars, target.name | Must be mirrored | No need to mirror |
| Part of the Mesh workflow | No | Yes, on Enterprise and Enterprise+ |
dependencies.yml sits at the root of the project and can hold both kinds of dependency: projects go under projects: and Hub packages under packages:. Keep using packages.yml when your package specifications use Jinja, for example a Git token read from an environment variable. dependencies.yml does not render Jinja and does not support private packages. The project name must match the upstream dbt_project.yml exactly, including case: jaffle_marketing, not JAFFLE_MARKETING.
Checkpoint 6 of 8· Fill the gap
The jaffle_marketing project needs dbt_utils as a package and the Finance team's models as a project. Which key completes this dependencies.yml?
packages:
- package: dbt-labs/dbt_utils
version: 1.1.1
? :
- name: jaffle_finance # case sensitive and matches the 'name' in the 'dbt_project.yml'Cross-project dependencies go under projects:. That tells dbt to resolve jaffle_finance through the metadata service instead of downloading its code.
Source: docs.getdbt.comEvery reference to a model from a project dependency uses the two-argument form, with the project name first. A one-argument ref looks only inside the current project. The upstream model must also be access: public. The default access is protected, which allows references only from within the model's own project.
select * from {{ ref('jaffle_finance', 'monthly_revenue') }}Once you commit the change, dbt resolves the dependency, makes sure upstream projects are built first, and shows lineage across projects in the DAG. If the Finance team later renames the model, changes its schema or bumps its version, the ref still resolves. It points at wherever Finance builds the model in production.
Checkpoint 7 of 8· Exam question
A platform team is preparing the `finance` project's `stg_orders` model to be consumed by other dbt projects through a cross-project ref. Which two conditions must both be true before that ref will resolve for a downstream consumer? (Select two.)(Select 2)
Correct answers: A, C — The `stg_orders` model's resource config sets `access: public`, since the default private access blocks any cross-project reference.; The `finance` project's designated deployment environment has recorded at least one successful job run that published metadata.
- A. This is correct. Models default to private access, which blocks any ref from outside their own project, so `stg_orders` must be explicitly marked `access: public` before another project can reference it at all.
- B. Project dependencies are declared under a `projects:` block in `dependencies.yml`, not pinned by version inside `packages.yml`; that file and version-pin mechanism belong to package dependencies, not project dependencies.
- C. This is correct. The upstream project's designated deployment environment must have completed a successful run so that its manifest metadata is published; without that run, the metadata service has nothing to serve to a consumer.
- D. Cross-project ref resolution depends on which environment is designated as the upstream project's metadata-publishing environment, not on the two projects' environments sharing an identical name string.
5.Projects that depend on each other
Two projects can depend on each other in both directions, as long as no individual model ends up in a cycle. Finance can use Marketing's public models while Marketing uses Finance's. dbt checks for cycles across projects and raises an error if it finds one.
A pair like this has to be set up step by step. Neither project can resolve a reference until the other has published public models from a deployment run. If you add both dependencies at once, whichever project runs first has nothing to resolve against.
Checkpoint 8 of 8· Put it in order
Put the setup of a two-way dependency between project_a and project_b in order.
- 1.project_b runs in a deployment environment and produces public models
- 2.project_a adds project_b as a dependency
- 3.project_b adds project_a as a dependency
- 4.project_a runs in a deployment environment and produces public models
Each project must run and publish public models before the other can depend on it. So project_b runs before project_a adds it as a dependency.
“Each project must run and produce public models before the original producer project can take a dependency on the original consumer project.”Source: docs.getdbt.com
Sources5
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.You should give one team separate dbt projects for dev and prod, such as "Marketing - Dev" and "Marketing - Prod".Why is that wrong?
Project names must be unique in the account. Separate dev and prod with environments, each of which can use its own connection.
Covered in Creating the new project in dbt
2.Installing the upstream project through packages.yml sets up a mesh dependency.Why is that wrong?
A package pulls in the full source code and is not part of the Mesh workflow. Cross-project references need a project entry in dependencies.yml.
Covered in Declaring the dependency and writing the ref
3.Once the dependency is declared, a one-argument ref('monthly_revenue') finds the upstream model.Why is that wrong?
Models from a project dependency must be referenced with the two-argument form that names the project.
Covered in Declaring the dependency and writing the ref
4.Project names in dependencies.yml are matched without regard to case.Why is that wrong?
The name must match the upstream dbt_project.yml exactly, including case.
Covered in Declaring the dependency and writing the ref
Practise it for real
Make a downstream project reference a public model from an upstream project through a project dependency.
1.In the upstream project, set access: public on the model you want to share, merge the change, and run a deployment job in the Production environment.
Why: Downstream references resolve only to public models, using metadata from a successful deployment run.
You should see: The job succeeds and the model shows as Public in the lineage view.
2.In the downstream project, create dependencies.yml at the root and list the upstream project's name under projects:.
Why: This declares a project dependency instead of installing the upstream code as a package.
You should see: The name matches the upstream dbt_project.yml exactly, including case.
3.In a downstream model, select from the upstream model with a two-argument ref: project name first, then model name.
Why: Project dependencies require the project name in the ref.
You should see: The model compiles against the upstream model's production location.
4.Commit the changes and check the downstream lineage.
Why: Once the dependency is configured, dbt resolves it and shows lineage across projects.
You should see: The upstream public model appears as a parent in the DAG.
Stuck? Get a nudge
If the ref fails to resolve, check whether the upstream model still has the default access, protected.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.https://docs.getdbt.com/docs/mesh/about-meshOfficial docs
“The number of models in your project is degrading performance and slowing down development.”
↩︎ When a second project is worth it, and where to cut“{{ ref() }}s now work across dbt projects on Enterprise and Enterprise+ plans.”
↩︎ When a second project is worth it, and where to cut“If you're just starting your dbt journey, don't worry about building a multi-project architecture right away.”
↩︎ When a second project is worth it, and where to cut“data contracts set explicit expectations on the shape of the data”
↩︎ Moving models into the new project - 2.
“Horizontal splits separate your DAG based on source or domain.”
↩︎ When a second project is worth it, and where to cut“handle breaking out the central platform project before you split the remainder into domains.”
↩︎ When a second project is worth it, and where to cut“projects are 1:1 aligned to teams, and each project has its own codebase in its own repository.”
↩︎ When a second project is worth it, and where to cut“a coordinated change across multiple projects very frequently (every week), or as a key prerequisite for ~20%+ of their work.”
↩︎ When a second project is worth it, and where to cut“Isolating earlier models for security and governance requirements to separate out and mask PII data so that downstream consumers can't access it”
↩︎ Checkpoint - 3.
“Your selectors(defined in selectors.yml): How do people already define resource groups?”
↩︎ When a second project is worth it, and where to cut“Create a new dbt_project.yml file in the subdirectory.”
↩︎ Moving models into the new project“Do one group at a time! Do not refactor as you migrate, however tempting that may be.”
↩︎ Moving models into the new project“If using sources, there are breakages in the lineage, as there's no real connection between the parent and child projects.”
↩︎ Moving models into the new project“Replace the {{ source() }} functions with cross project {{ ref() }} functions. Remove the unnecessary source definitions.”
↩︎ Checkpoint - 4.https://docs.getdbt.com/guides/mesh-qsOfficial docs
“From Account settings, go to Projects. Click New project.”
↩︎ Creating the new project in dbt“You must set up a "Production" or "Staging" deployment environment for each project you want to "mesh" together.”
↩︎ Creating the new project in dbt“Create and define at least one model as “public””
↩︎ Creating the new project in dbt“the always-enterprising and fictional account "Jaffle Labs" will create two projects for their data analytics and finance team”
↩︎ Creating the new project in dbt“By default, model access is set to "protected", which means they can only be referenced within the same project.”
↩︎ Declaring the dependency and writing the ref - 5.
“Each project name must be unique in your dbt account.”
↩︎ Creating the new project in dbt“Connections are reusable across projects and environments.”
↩︎ Creating the new project in dbt“so long as the new dependency doesn't introduce any node-level cycles.”
↩︎ Moving models into the new project“You don't need to mirror any conditional configuration of the upstream project such as vars, environment variables, or target.name.”
↩︎ Declaring the dependency and writing the ref“While you can select those models, you can't actually build them.”
↩︎ Declaring the dependency and writing the ref“Private packages are not supported in dependencies.yml because they intentionally don't support Jinja rendering or conditional configuration.”
↩︎ Declaring the dependency and writing the ref“dbt checks for cycles across projects and raises errors if any are detected.”
↩︎ Projects that depend on each other“When setting up projects that depend on each other, it's important to do so in a stepwise fashion.”
↩︎ Projects that depend on each other“Using a metadata service that runs behind the scenes, dbt resolves references on-the-fly to public models defined in other projects.”
↩︎ Key concept“avoid creating projects for Jaffle Marketing - Dev and Jaffle Marketing - Prod; use environment-level isolation instead.”
↩︎ Exam trap 1“Something to note is that it doesn't contribute to the dbt Mesh workflow.”
↩︎ Exam trap 2“Models referenced from a project-type dependency must use two-argument ref, including the project name”
↩︎ Exam trap 3“the project name must exactly match the name in your dependencies.yml. For example, jaffle_marketing, not JAFFLE_MARKETING.”
↩︎ Exam trap 4“avoid creating projects for Jaffle Marketing - Dev and Jaffle Marketing - Prod; use environment-level isolation instead.”
↩︎ Prediction“Define a Production deployment environment in the upstream project and make sure at least one deployment job has run successfully there.”
↩︎ Checkpoint“Each project must run and produce public models before the original producer project can take a dependency on the original consumer project.”
↩︎ Checkpoint