dbt Packages: Importing Reusable Modules Into Your Project
Somewhere between โwrite a macro for itโ and โsurely someone has already solved thisโ lies a genuinely large ecosystem of dbt packages โ pre-built collections of macros, models, and tests that you can install into your own project the way youโd install a library in any other language. Before writing a generic-purpose macro from scratch, itโs worth checking whether a package already does it, tested against thousands of other projects.
What a Package Actually Is
A dbt package is just another dbt project, structured so it can be imported into yours. It typically contributes macros, and sometimes models or tests, that become available in your project as if youโd written them yourself.
Installing a Package
Packages are declared in a packages.yml file at your project root, then installed with a dedicated command.
packages: - package: dbt-labs/dbt_utils version: [">=1.1.0", "<2.0.0"] - package: calogica/dbt_expectations version: [">=0.10.0", "<0.11.0"]dbt depsdbt deps downloads each package into a dbt_packages/ directory locally. That directory should be gitignored โ packages are a dependency, not something you commit to your own repo, the same way you wouldnโt commit node_modules.
dbt_packages/Using Macros From an Installed Package
Once installed, a packageโs macros are called with the package name as a namespace prefix.
select {{ dbt_utils.generate_surrogate_key(['customer_id', 'order_date']) }} as order_key, customer_id, order_date, amountfrom {{ ref('stg_orders') }}This calls generate_surrogate_key from the dbt_utils package exactly like calling any macro youโd written yourself โ the only difference is the dbt_utils. prefix identifying which package it comes from.
Version Pinning and Why It Matters
The version range in packages.yml isnโt a formality โ packages do introduce breaking changes between major versions, and an unpinned or overly loose range means a dbt deps run six months from now could silently pull in a version that changes macro behavior underneath you.
packages: # Loose โ risky, could pull in breaking changes unexpectedly - package: dbt-labs/dbt_utils version: ">=1.0.0"
# Pinned to a compatible range โ the safer default - package: dbt-labs/dbt_utils version: [">=1.1.0", "<2.0.0"]Treat package versions the same way youโd treat dependency versions in any other software project โ pin them, and upgrade deliberately with a changelog review, not implicitly on every dbt deps run.
Installing From Git, Not Just the Package Hub
Not every useful package is published to dbtโs public Package Hub. Private, internal, or unpublished packages can be installed directly from a Git repository.
packages: - git: "https://github.com/your-org/internal-dbt-macros.git" revision: v2.3.0Pinning to a specific tag or commit (revision) matters even more here than with published packages, since thereโs no registry enforcing semantic versioning discipline on an internal repo.
Building an Internal Package for Your Own Organization
Once a set of macros is genuinely reusable across multiple dbt projects within your company โ not just multiple models within one project โ itโs worth extracting them into their own internal package rather than copy-pasting macro files between repos.
internal-dbt-macros/ dbt_project.yml macros/ generate_surrogate_key.sql standard_grants.sql custom_tests/ not_null_proportion.sqlAny other project in the organization can then install this exactly like a public package, keeping shared conventions in one maintained place instead of drifting apart across teamsโ individual projects.
Packages vs. Writing Your Own Macro
The decision isnโt always obvious. A useful heuristic:
| Situation | Recommended approach |
|---|---|
| Common, generic need (surrogate keys, date spines, pivots) | Check dbt_utils first |
| Data quality testing beyond dbtโs built-in generic tests | Check dbt_expectations or dbt_utils test macros |
| Business-logic-specific, unlikely to be reusable elsewhere | Write your own macro |
| Reused across multiple internal projects | Extract into an internal package |
Writing a macro that duplicates something dbt_utils already provides, tested and maintained by a large community, is rarely a good use of time compared to just installing the package.
Upgrading a Package Deliberately
When a genuinely new package version is worth adopting โ a bug fix, a new macro you actually need โ the safe upgrade path is a dedicated branch, not a routine dbt deps that happens to pull a newer version silently.
# Beforepackages: - package: dbt-labs/dbt_utils version: [">=1.1.0", "<1.2.0"]
# After, on its own branch, reviewed like any other dependency bumppackages: - package: dbt-labs/dbt_utils version: [">=1.2.0", "<1.3.0"]dbt depsdbt buildRunning the full build after a version bump, before merging, catches any breaking macro signature changes immediately โ treating this exactly like upgrading any other software dependency, rather than assuming package updates are always safe by default.
Common Mistakes
Committing dbt_packages/ to version control. This bloats your repo with third-party code that should be fetched fresh via dbt deps, not tracked โ always gitignore it.
Forgetting to run dbt deps after adding a package. Editing packages.yml alone doesnโt install anything; it just declares intent. dbt deps is a separate, required step, and CI pipelines need to run it before any dbt run or dbt test.
Unpinned version ranges causing silent breakage. A wide-open version constraint means a routine dbt deps months later can pull in a major version bump with different macro signatures, breaking models that were working fine yesterday.
Summary
| Element | Purpose |
|---|---|
packages.yml | Declares which packages your project depends on |
dbt deps | Downloads declared packages into dbt_packages/ |
package_name.macro_name() | Calls a macro from an installed package |
| Version pinning | Prevents silent breaking changes on future installs |
Packages are how the dbt ecosystem avoids everyone reinventing the same surrogate-key macro. Before writing something generic from scratch, check whether dbt_utils โ covered next in dbt_utils Package โ already solves it.