What you will be able to do
- Explain what a dbt package adds to your project and how to reference its models, sources and macros
- Choose between packages.yml and dependencies.yml for a given dependency setup
- Declare Hub, git, local, tarball and native private packages with the right keys
- Use dbt deps, its flags and package-lock.yml to get repeatable installs
- Override a package macro with a root-level dispatch config and predict which implementation dbt picks
Key concept
dbt package — A dbt package is a complete dbt project that you install inside your own. Once installed, its models, sources and macros belong to your project as if you had written them yourself.
1.What a package adds to your project
A dbt package is a library for analytics code: someone else's dbt project that you install so you don't have to rewrite problems they have already solved. Many analytics problems look the same from one organization to the next. Examples include turning Segment pageviews into sessions, generating SQL to union two relations, building a surrogate key, and writing audit queries. Packages are the dbt form of the [modularity and DRY reuse](lesson:1.3) you practise inside one project, applied across projects.
Installing a package merges it into your project. Every resource in the package becomes part of your project, with four practical effects:
- Models in the package are materialized when you run dbt run.
- Your own models can ref the package's models.
- Your models can use source to point at sources the package defines.
- You can call the package's macros from your own code.
The first effect catches people out. Installing a modelling package such as dbt-labs/snowplow adds its models to your next run, not just a few helper functions.
Two boundaries are worth knowing. dbt packages have nothing to do with Python packages: you define them and install them through a separate mechanism. And packages fit generic problems best. Shared macros, and models for a common dataset such as MailChimp or Snowplow, make good packages. Business-specific logic such as marketing attribution or monthly recurring revenue does not.
Checkpoint 1 of 8· Check yourself
Your team installs a package that contains both macros and models for a SaaS dataset. What happens the next time someone runs dbt run in your project?
Once a package is installed, all of its resources belong to your project, so dbt run materializes its models alongside yours.
“Models in the package will be materialized when you dbt run.”Source: docs.getdbt.com
2.Declaring dependencies in packages.yml or dependencies.yml
Packages are declared in a YAML file that sits at the same level as dbt_project.yml. You list the packages there and then run dbt deps to download them. You can name the file packages.yml or dependencies.yml, and the choice depends on what else the file has to do.
Jinja decides most cases. packages.yml supports Jinja rendering, a capability it keeps for historical reasons. That lets a package specification read env_var, var (only variables passed with --vars), target or dbt_version. It is also the only option when a private Git package uses the older git-token method, where a token from an environment variable is inserted into the URL. Only packages.yml and profiles.yml accept secret environment variables carrying the DBT_ENV_SECRET_ prefix. dependencies.yml deliberately does not render Jinja, which keeps its configuration static and predictable.
packages:
- package: dbt-labs/dbt_utils
version: "{{ env_var('DBT_UTILS_VERSION') }}"dependencies.yml can hold two kinds of dependency. Package dependencies are the library-style source code covered on this page. Project dependencies are the cross-project references used in dbt Mesh, which only dependencies.yml can declare. If you use Hub packages alone, keep packages.yml. If you mix Mesh project references with non-private packages, a single dependencies.yml keeps everything in one place. Native private packages, declared with the private: key, also work in dependencies.yml, because they need no Jinja. If your existing packages.yml contains no Jinja, you can simply rename it.
Checkpoint 2 of 8· Put it in order
Put the steps for adding a package to a project in order
- 1.Specify each package using one of the supported syntaxes
- 2.Create packages.yml or dependencies.yml at the same level as dbt_project.yml
- 3.Run dbt deps to install the packages
The file has to exist, next to dbt_project.yml, and list the packages before dbt deps has anything to install.
“Run dbt deps to install the package(s).”Source: docs.getdbt.com
3.Hub, git, local, tarball and private packages
Each entry under packages: starts with a key that says where the code lives, and that key decides which other fields are required. The table pairs each key with what it needs and the situation it suits.
| Key | Required companion field | Use it when |
|---|---|---|
| package | version (exact or a range) | The package is published on dbt Hub |
| git | revision is optional: a branch, tag, or full 40-character commit hash | The package lives in a Git repository |
| local | A filesystem path, e.g. /opt/dbt/redshift | The package is on the same machine |
| tarball | name, the subfolder of dbt_packages to install into | Security rules allow downloads only from internal hosts such as Artifactory |
| private | provider: github, gitlab or ado | A private repo, authenticated through your existing Git integration or SSH key |
Hub packages must specify a version. Hub releases follow semantic versioning, so the recommended pattern is a range that pins one minor release and accepts its patch updates. Prereleases such as a1, b2 or rc3 are left out of resolution by default. You get one only by naming that prerelease version exactly, or by setting install_prerelease: true together with a range that includes it.
packages:
- package: dbt-labs/snowplow
version: [">=0.7.0", "<0.8.0"]Prefer the Hub when packages share dependencies. Suppose your project uses Snowplow and Stripe, and both of them depend on dbt-utils. Installed from the Hub, dbt resolves that shared dependency once. The other installation methods cannot handle the duplicate dbt-utils. Choose git when you need a branch or a specific commit, and private when the repo is internal. With private, the dbt platform authenticates through its configured GitHub, GitLab or Azure DevOps integration. Locally, on dbt v1.12+ or dbt v2, it uses your SSH key. Either way you don't provision an access token.
packages:
- git: "https://github.com/dbt-labs/dbt-utils.git" # git URL
revision: 0.9.2 # tag or branch nameCheckpoint 3 of 8· Match them up
Match each package key to the situation it fits
Tap a term, then the definition that fits it.
Only Hub packages get duplicate-dependency handling. git accepts a revision, tarball covers internal-only hosting, and private reuses your Git integration with no token.
“Native private packages let you install packages from supported private Git repos using the private key”Source: docs.getdbt.com
Checkpoint 4 of 8· Exam question
A team's packages.yml contains: ``` packages: - git: "https://github.com/dbt-labs/dbt-utils.git" revision: 1.1.1 - git: "https://github.com/dbt-labs/dbt-expectations.git" revision: 0.10.1 ``` dbt-expectations itself depends on dbt-utils internally. Running `dbt deps` fails with a version resolution error instead of silently merging the two dbt-utils installs. Which factor is the root cause?
Correct answer: B — Both packages are installed with the `git:` key, and dbt cannot automatically resolve duplicate transitive dependencies for git-sourced packages the way it does for packages installed from dbt Hub.
- A. Incorrect. dbt does not compare publish dates between packages, and a newer git revision does not by itself trigger a resolution failure.
- B. Correct. Git-sourced packages cannot have their duplicate transitive dependencies automatically reconciled by dbt, unlike packages installed from dbt Hub, so two git packages that both pull in dbt-utils can collide.
- C. Incorrect. Listing multiple packages under one `packages:` key is the normal, supported way to declare dependencies and does not itself cause conflicts.
- D. Incorrect. Pinning to an exact tag is a normal and recommended pattern; the resolution failure comes from the git source type, not from using an exact version instead of a range.
Sources1
4.dbt deps and the package-lock.yml file
dbt deps downloads packages into dbt_packages, and by default Git ignores that folder, so the package source code isn't duplicated in your repository. To install somewhere else, set packages-install-path in dbt_project.yml, for example packages-install-path: packages.
package-lock.yml is what makes installs repeatable. On its first run, dbt deps writes a lock file in the project root. It records the exact resolved version of every package, including commit SHAs. Later runs install from the lock file. As long as your packages file is unchanged, every developer and every environment gets the same versions, even after newer releases appear. That stability is the reason to commit package-lock.yml to version control. If you git-ignore it instead, builds become less predictable, and you should then pin third-party package versions in the packages file itself.
The lock file contains a sha1_hash of your packages config, which is how dbt notices edits. If you add a package or widen a range, the next dbt deps resolves the dependencies again and rewrites the lock. Three flags change this default behaviour:
- --upgrade refreshes every package even when the packages file is unchanged. This is useful for pulling the latest commits from the main branch of an internal Git package, though forced upgrades can cause build inconsistencies.
- --lock updates the lock file to match config changes without installing anything.
- --add-package writes the entry for you, for example dbt deps --add-package dbt-labs/dbt_utils@1.0.0. Hub is the default type; add --source git or --source local for the other two.
Checkpoint 5 of 8· Fill the gap
An internal Git package tracks its main branch. Your packages.yml has not changed, but you want the latest commits. Which flag completes the command?
dbt deps ? With an unchanged config, plain dbt deps reinstalls the locked versions. --upgrade forces it to update every package anyway. --lock only rewrites the lock file and installs nothing.
Source: docs.getdbt.comCheckpoint 6 of 8· Exam question
A platform team wants dbt-utils to automatically pick up new patch releases whenever `dbt deps` reruns in CI, but wants CI to fail loudly once a new minor or major release changes behavior. Which packages.yml entry satisfies this requirement?
Correct answer: C — Pin dbt-utils with `version: [">=0.7.0", "<0.8.0"]`, so `dbt deps` installs any 0.7.x patch release automatically and errors once dbt-utils reaches version 0.8.0.
- A. Incorrect. Pinning to one exact version and forcing a reinstall of that same version never lets CI pick up a newer patch release automatically.
- B. Incorrect. A `revision` under a `git:` entry is a tag, branch name, or commit hash, not a semantic version range, so it cannot express "any 0.7.x patch."
- C. Correct. A Hub package version range lets `dbt deps` resolve to the newest release inside the bounds automatically while erroring once the release moves outside that range.
- D. Incorrect. Tracking the default branch with no pin means any commit, including breaking minor or major changes, gets picked up silently with no CI failure.
5.Overriding package macros with dispatch
Packages that run on many warehouses rely on dispatch. A generic macro hands the work to an implementation chosen for the current adapter. dbt searches for macros named with the adapter prefix, such as snowflake__concat or redshift__concat, and falls back to default__concat when no adapter-specific version exists. Dispatched macros inside a package pass a macro_namespace, for example adapter.dispatch('concat', 'dbt_utils'). That namespace tells dbt which package to search.
That namespace lets you override a package without forking it. Writing your own concat macro changes only your own calls to it. Macros inside dbt_utils, such as surrogate_key, still call dbt_utils.concat. To route those calls through your version as well, add a dispatch config with a search_order for the dbt_utils namespace. The config only works in your root dbt_project.yml; dbt ignores dispatch configs in the project files of installed packages.
dispatch:
- macro_namespace: dbt_utils
search_order: ['my_project', 'dbt_utils']dbt only ever considers implementations that are compatible with the current adapter, and within those it finishes searching one package before moving to the next. On Postgres the lookup is my_project.postgres__concat, then my_project.default__concat, then dbt_utils.postgres__concat, and finally dbt_utils.default__concat, which is found and used. The same pattern lets an internal helper package override dbt's own global macros, such as generate_schema_name: each root project lists that package ahead of dbt in the search_order for the dbt namespace. Some things cannot be overridden this way. ref, source and config are context properties, not dispatched global macros, so dispatch never reaches them.
Checkpoint 7 of 8· Check yourself
A package maintainer puts a dispatch block in the package's own dbt_project.yml so that the package's macros override dbt_utils for every project that installs it. What happens?
dbt reads dispatch configs only from the root project, so each project that installs the package must add the search_order itself.
“dbt will ignore any dispatch configs defined in the project files of installed packages.”Source: docs.getdbt.com
Checkpoint 8 of 8· Exam question
A developer ran `dbt deps` last week, and dbt-utils' Hub listing has since published a new minor release. Today they rerun plain `dbt deps` on the same machine and dbt-utils does not update. Which two statements correctly explain this behavior?(Select 2)
Correct answers: B, E — The first `dbt deps` run generated a package-lock.yml file recording the installed version, and a rerun reinstalls from that file instead of re-checking dbt Hub.; Picking up the newer release requires `dbt deps --upgrade`, which re-resolves package versions against dbt Hub and ignores the existing package-lock.yml file.
- A. Incorrect. dbt Hub publishing is not gated by dbt Cloud plan tier, and CLI-only projects install directly from Hub the same as any other project.
- B. Correct. `dbt deps` writes a package-lock.yml recording the exact installed version, and a subsequent plain run reinstalls from that lock file rather than re-resolving against dbt Hub.
- C. Incorrect. Deleting and recreating packages.yml is unnecessary; the lock file, not the packages.yml content, is what pins the installed version.
- D. Incorrect. `require-dbt-version` constrains which dbt version can run the project; it has no effect on which package version gets installed.
- E. Correct. `dbt deps --upgrade` explicitly re-resolves package versions from dbt Hub and disregards the package-lock.yml file, which is exactly what picking up a new release requires.
- F. Incorrect. There is no thirty-day metadata cache in dbt's package resolution; the behavior here is fully explained by the lock file.
Sources8
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Installing dbt-utils from git handles shared dependencies just as well as installing it from the Hub.Why is that wrong?
Only Hub installation lets dbt resolve a dependency shared by several packages. Git and the other methods cannot handle the duplicate dbt-utils.
2.Every dbt deps run fetches the newest version allowed by your version range.Why is that wrong?
When the packages file is unchanged, dbt deps installs the versions recorded in package-lock.yml. Newer releases come in only after a config change or with --upgrade.
Covered in dbt deps and the package-lock.yml file
3.A dispatch config can replace how ref or source behave for an installed package.Why is that wrong?
ref, source and config are context properties, not dispatched global macros, so dispatch cannot override them.
Covered in Overriding package macros with dispatch
4.A private Git package that uses an environment-variable token can be declared in dependencies.yml.Why is that wrong?
The git-token method relies on Jinja, which dependencies.yml does not render, so those packages must go in packages.yml.
Covered in Declaring dependencies in packages.yml or dependencies.yml
Practise it for real
Add dbt-utils to a project, and leave it pinned and reproducible for the rest of the team.
1.Run dbt deps --add-package dbt-labs/dbt_utils@1.0.0 from the project root.
Why: The command writes the package entry for you, so you don't have to remember the exact syntax. Hub is the default type.
You should see: The package appears in your packages file and is installed into the dbt_packages directory.
2.Open package-lock.yml in the project root.
Why: This file is what keeps every environment on the same versions.
You should see: It records the exact resolved version of dbt_utils and a sha1_hash of your packages config.
3.Commit package-lock.yml and the packages file. Leave dbt_packages out of the commit.
Why: Teammates and jobs will install from the lock file, and Git ignores dbt_packages by default.
You should see: A teammate who runs dbt deps gets exactly the same dbt_utils version.
4.Change the version in the packages file and run dbt deps --lock.
Why: This shows how dbt detects a config change without installing anything.
You should see: The lock file is updated to the new resolution, and no packages are installed.
Stuck? Get a nudge
If dbt deps keeps installing an old version after a new release, check whether the packages file actually changed. If it didn't, the lock file is doing its job; use --upgrade to move forward.
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/build/packagesOfficial docs
“You can use ref in your own models to refer to models from the package.”
↩︎ What a package adds to your project“It's important to note that defining and installing dbt packages is different from defining and installing Python packages”
↩︎ What a package adds to your project“This should be at the same level as your dbt_project.yml file.”
↩︎ Declaring dependencies in packages.yml or dependencies.yml“packages.yml supports Jinja rendering for historical reasons, allowing dynamic configurations.”
↩︎ Declaring dependencies in packages.yml or dependencies.yml“Project dependencies are designed for the dbt Mesh and cross-project reference workflow”
↩︎ Declaring dependencies in packages.yml or dependencies.yml“Hub packages require a version to be specified”
↩︎ Hub, git, local, tarball and private packages“By default, dbt deps will not include prerelease versions when resolving package dependencies.”
↩︎ Hub, git, local, tarball and private packages“Where possible, we recommend installing packages via dbt Hub, since this allows dbt to handle duplicate dependencies.”
↩︎ Hub, git, local, tarball and private packages“Where name: 'dbt_utils' specifies the subfolder of dbt_packages that's created for the package source code to be installed within.”
↩︎ Hub, git, local, tarball and private packages“a specific commit (full 40-character hash)”
↩︎ Hub, git, local, tarball and private packages“dbt packages are in fact standalone dbt projects, with models, macros, and other resources that tackle a specific problem area.”
↩︎ Key concept“In comparison, other package installation methods are unable to handle the duplicate dbt-utils package.”
↩︎ Exam trap 1“Models in the package will be materialized when you dbt run.”
↩︎ Checkpoint“Run dbt deps to install the package(s).”
↩︎ Checkpoint“Native private packages let you install packages from supported private Git repos using the private key”
↩︎ Checkpoint - 2.https://docs.getdbt.com/guides/building-packagesOfficial docs
“Packages are not a good fit for sharing models that contain business-specific logic”
↩︎ What a package adds to your project - 3.
“Only packages.yml and profiles.yml support environment variables for secure values (using the DBT_ENV_SECRET_ prefix).”
↩︎ Declaring dependencies in packages.yml or dependencies.yml - 4.
“If you're using native private packages, you can define them in the dependencies.yml file.”
↩︎ Declaring dependencies in packages.yml or dependencies.yml“If you're using the git token method, you must define them in the packages.yml file instead of the dependencies.yml file.”
↩︎ Exam trap 4 - 5.
“Optionally specify a custom directory where packages are installed when you run the dbt deps command.”
↩︎ dbt deps and the package-lock.yml file - 6.https://docs.getdbt.com/reference/commands/depsOfficial docs
“The package-lock.yml file ensures consistent and repeatable installs across all environments.”
↩︎ dbt deps and the package-lock.yml file“To maintain consistency, commit the package-lock.yml file to version control.”
↩︎ dbt deps and the package-lock.yml file“dbt deps --lock to update the lock file based on changes to the packages config without installing the packages”
↩︎ dbt deps and the package-lock.yml file“Forcing package upgrades may introduce build inconsistencies unless carefully managed.”
↩︎ dbt deps and the package-lock.yml file“Use the --source flag to specify the type of package to be installed:”
↩︎ dbt deps and the package-lock.yml file“When you run dbt deps, dbt installs packages based on the versions locked in the package-lock.yml.”
↩︎ Exam trap 2 - 7.
“it may be due to an old version of your dbt_utils package that isn't compatible with your current dbt version.”
↩︎ dbt deps and the package-lock.yml file - 8.
“If dbt does not find an adapter-specific implementation, it will dispatch to the default implementation.”
↩︎ Overriding package macros with dispatch“Dispatched macros from packages must provide the macro_namespace argument”
↩︎ Overriding package macros with dispatch“Note that this config must be specified in the user's root dbt_project.yml.”
↩︎ Overriding package macros with dispatch“this functionality enables users of my package to extend, reimplement, or override default behavior, without needing to fork the package's source code.”
↩︎ Overriding package macros with dispatch“Certain functions like ref, source, and config can't be overridden with a package using the dispatch config.”
↩︎ Exam trap 3“But dbt will prioritize package specificity over adapter specificity.”
↩︎ Prediction“dbt will ignore any dispatch configs defined in the project files of installed packages.”
↩︎ Checkpoint