CertSafari
    DBT Analytics Engineering· Lessons

    Domain 1 · Lesson 7/31

    dbt packages: installing, pinning and overriding them

    Using dbt Packages

    15 min read
    3.23% of exam
    8 sources
    Published 12 Oct 2026
    Docs as of 12 Oct 2026

    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?

    Sources12

    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.

    Jinja in packages.yml: the version comes from an environment variable, which dependencies.yml cannot doyaml
    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. 1.Specify each package using one of the supported syntaxes
    2. 2.Create packages.yml or dependencies.yml at the same level as dbt_project.yml
    3. 3.Run dbt deps to install the packages

    Sources134

    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.

    Package specification keys and what each one requires
    KeyRequired companion fieldUse it when
    packageversion (exact or a range)The package is published on dbt Hub
    gitrevision is optional: a branch, tag, or full 40-character commit hashThe package lives in a Git repository
    localA filesystem path, e.g. /opt/dbt/redshiftThe package is on the same machine
    tarballname, the subfolder of dbt_packages to install intoSecurity rules allow downloads only from internal hosts such as Artifactory
    privateprovider: github, gitlab or adoA 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.

    A Hub package pinned to the 0.7 minor release, accepting any patchyaml
    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.

    A git package pinned to a tag or branch with revisionyaml
    packages:
      - git: "https://github.com/dbt-labs/dbt-utils.git" # git URL
        revision: 0.9.2 # tag or branch name

    Checkpoint 3 of 8· Match them up

    Match each package key to the situation it fits

    Tap a term, then the definition that fits it.

    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?

    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  ? 

    Checkpoint 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?

    Sources567

    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.

    Root dbt_project.yml: search my_project before dbt_utils for dbt_utils macrosyaml
    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?

    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)

    Sources8

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 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.

      Covered in Hub, git, local, tarball and private packages

    2. 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. 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. 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. 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. 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. 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. 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. 1.
      “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. 2.
      “Packages are not a good fit for sharing models that contain business-specific logic”
      ↩︎ What a package adds to your project
    3. 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. 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. 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. 6.
      “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. 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. 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

    Ready to test yourself?

    Practise the 11 questions on this subdomain.

    Spotted a mistake, or was something unclear? Tell us.