Scaling Analytics Without Chaos: A Modular dbt Mesh for Multi-Team Setups

Introduction

As data platforms grow, teams often encounter the same challenge: multiple projects need the same foundational datasets, yet duplicating transformations across projects quickly becomes difficult to maintain.

A scalable approach is to separate shared data assets from domain-specific transformations. In this architecture, a shared dbt project produces reusable, governed models, while consumer projects build use-case specific logic on top of those shared assets.

dbt Mesh Architecture

(Fig 1: High-level architectural view of a Mesh with two dbt projects)

The diagram above illustrates a Mesh pattern, where metadata artifacts are used to expose selected models from the shared project to downstream consumer projects using dbt-loom. Mesh allows for better reusability of data assets across projects without compromising on the “governance independence” of consumer projects.

Architecture Overview

At a high level, the architecture consists of two types of dbt projects:

1. Common (Shared) Project

A centralized dbt project responsible for producing reusable models that can be consumed by multiple downstream teams or domains.

2. Consumer Projects

Independent dbt projects that implement domain-specific transformations while reusing the shared models exposed by the shared project.

3. dbt-loom

A dbt package that allows consumer projects to reference models from the shared project using its compiled metadata rather than importing its code directly.

This approach supports:

  • Modular development
  • Shared governance
  • Independent deployment cycles
  • Reusable, well-defined data assets

The Shared Project

Imagine you have a shared dbt project responsible for building commonly used datasets that multiple teams rely on.

For example, this project might produce shared entities such as:

  • dim_customer
  • dim_product
  • dim_item
  • Shared inventory or transaction datasets

Rather than having each team implement these transformations independently, the shared project defines them once and makes them available to downstream projects.

Not every model in the shared project is necessarily meant to be consumed externally. Only a subset of models is exposed for reuse. These models are marked with:

access: public

Models with access: public become available to downstream projects, while other internal models remain private.

Artifact Generation

When the shared project runs:

dbt build --target <environment>

dbt produces artifact metadata describing the project’s models and dependencies.

Important artifacts include:

  • manifest.json
  • run_results.json
  • catalog.json

The key artifact for cross-project consumption is:

manifest.json

This file contains metadata describing:

  • Models
  • Dependencies
  • Lineage
  • Documentation
  • Access configuration

In order to only update your manifest.json, you can also run:

dbt parse

Publishing Artifacts

The manifest generated by the shared project must be stored somewhere accessible to downstream projects.

Typical storage locations include:

Cloud Storage

For example:

  • Databricks Volumes
  • Cloud object storage
  • Shared filesystem

 Local Development

For development environments, the artifact may also be stored in a local directory.

Only the manifest.json is required for downstream integration.

Consumer Projects

Consumer projects are responsible for building domain-specific models tailored to a particular use case.

For example, a consumer project might implement models related to:

  • Inventory analytics
  • Logistics reporting
  • Replenishment logic
  • Operational dashboards

These projects typically include their own:

  • Source definitions
  • Staging models
  • Intermediate transformations
  • Final marts

Referencing Shared Models

Without a dedicated mechanism, consumer projects might reference shared tables as sources:

select *
from {{ source('item_database', 'dim_item') }}

While this works, it has a major limitation: the lineage is lost. The downstream project cannot see how the table was created or what transformations contribute to it.

However, instead of recreating shared business entities, they reuse the ones produced by the shared project.

Using dbt-loom

dbt-loom solves this problem by allowing consumer projects to reference upstream models using metadata from the shared project’s manifest.

Instead of treating upstream tables as opaque sources, dbt-loom allows them to behave like normal dbt models.

Consumer projects can write:

select *
from {{ ref('common_project', 'dim_item') }}

Even though the model exists in another dbt project.

At compile time, dbt-loom:

  • Reads the upstream manifest.json
  • Identifies models marked with access: public
  • Injects those models into the consumer project’s dependency graph

As a result:

  • Lineage becomes visible across projects
  • Documentation integrates seamlessly
  • Dependencies remain explicit

Installing dbt-loom

Consumer projects install dbt-loom as a dependency:

packages:
  - dbt-loom

Then install dependencies:

dbt deps

Configuring dbt-loom

A configuration file points dbt-loom to the upstream manifest.

Example configuration (dbt_loom.config):

manifests:
  - name: shared_project
    type: file
    config:
      path: /Volumes/shared_project/artifacts/manifest.json

Here it is assumed that the data warehouse/lakehouse you are working with is Databricks.

When you are developing locally with dbt-core, you can specify the config path of the manifest like:

dbfs:///Volumes/my_catalog/artifacts/dbt_target/manifest.json

in a local dbt-loom config file that can be named:

dbt_loom_local.config.yml

The default value of the environment variable DBT_LOOM_CONFIG is:

dbt_loom.config

When you are running dbt locally you need dbt-loom to use the config file:

dbt_loom_local.config.yml

For this, in Windows you need to set the variable correctly using:

$env:DBT_LOOM_CONFIG="dbt_loom_local.config.yml"

Run the following command to check if the environment variable is set correctly:

$env:DBT_LOOM_CONFIG

Development Workflow

The development workflow typically looks like this:

Shared Project

  • Build models
  • Generate artifacts
  • Publish manifest.json

Consumer Project

  • Install dbt-loom
  • Configure access to the shared manifest
  • Reference shared models using ref()
  • Build downstream transformations

Advantages of a dbt Mesh

This architecture provides several benefits. This is nicely summarized by dbt here: dbt Mesh FAQs | dbt Developer Hub, but it is still worth summarizing the most significant benefits:

Model Reuse

Common datasets are defined once and reused across multiple projects.

Clear Ownership

The shared project owns canonical entities, while consumer projects own domain-specific logic.

Independent Deployment

Projects can be developed and deployed independently.

Improved Lineage

Because dbt-loom injects upstream models into the DAG, lineage remains visible across projects.

Reduced Duplication

Teams avoid recreating the same transformations in multiple repositories.

Example Scenario

Suppose multiple teams require a dataset called:

dim_item

Without a shared project, each team might implement its own version.

With this architecture:

Shared Project
   └── dim_item

Consumer projects simply reference it:

select *
from {{ ref('common_project', 'dim_item') }}

The transformation remains centralized, governed, and reusable.

Conclusion

Separating shared data assets from domain-specific transformations enables a modular and scalable dbt architecture. By exposing a subset of models from a shared project and allowing downstream projects to reference them through metadata, teams can maintain clear ownership boundaries while still promoting reuse.

Using dbt-loom to connect these projects preserves lineage, documentation, and dependency visibility across repositories, making the overall platform easier to understand, maintain, and extend as new use cases emerge.

As data platforms grow, teams often encounter the same challenge: multiple projects need the same foundational datasets, yet duplicating transformations across projects quickly becomes difficult to maintain.

A scalable approach is to separate shared data assets from domain-specific transformations. In this architecture, a shared dbt project produces reusable, governed models, while consumer projects build use-case specific logic on top of those shared assets.

Leave a Reply

Your email address will not be published. Required fields are marked *