Overrides and resolutions

Control metadependency versions with npm overrides and Yarn resolutions

Bun supports npm's "overrides" and Yarn's "resolutions" in package.json. Both specify a version range for metadependencies, the dependencies of your dependencies.

package.json
{
  "name": "my-app",
  "dependencies": {
    "foo": "^2.0.0"
  },
  "overrides": { 
    "bar": "~4.4.0" 
  } 
}

By default, Bun installs the latest version of all dependencies and metadependencies, according to the ranges specified in each package's package.json. Say your project has one dependency, foo, which in turn depends on bar. That makes bar a metadependency of your project.

package.json
{
  "name": "my-app",
  "dependencies": {
    "foo": "^2.0.0"
  }
}

When you run bun install, Bun installs the latest version of each package.

tree layout of node_modules
node_modules
├── foo@2.3.4
└── bar@4.5.6

If a security vulnerability is introduced in bar@4.5.6, you may want to pin bar to an older version that doesn't have it. That's what "overrides" and "resolutions" are for.


"overrides"#

Add bar to the "overrides" field in package.json. Bun defers to the specified version range when determining which version of bar to install, whether it's a dependency or a metadependency.

package.json
{
  "name": "my-app",
  "dependencies": {
    "foo": "^2.0.0"
  },
  "overrides": { 
    "bar": "~4.4.0" 
  } 
}

Bun only reads overrides from the root package.json, not from workspace packages. Overrides apply to peerDependencies as well.

Every install also checks the lockfile against the overrides whose value is a version or a range. If the lockfile has a version that such an override does not allow (after a merge, or a migration from another package manager), bun install resolves that package again, and bun install --frozen-lockfile fails and names it. The check leaves these entries as they are:

  • a bundled dependency and an optional peer dependency
  • a peer or optional dependency that shares its version with a dependency the override does not apply to
  • a name that one package.json lists in two groups, when the override applies to only one of the two ranges

"resolutions"#

"resolutions" is Yarn's alternative to "overrides", with similar syntax. Bun supports it to help projects migrate from Yarn. When a package.json has both fields, Bun uses only "overrides" and does not read "resolutions".

package.json
{
  "name": "my-app",
  "dependencies": {
    "foo": "^2.0.0"
  },
  "resolutions": { 
    "bar": "~4.4.0" 
  } 
}

Values#

A value can be any dependency specifier, not only a version range. npm: swaps a package for a fork, and catalog: (or catalog:<name>) keeps the overridden version in sync with a workspace catalog:

package.json
{
  "name": "my-app",
  "workspaces": {
    "packages": ["packages/*"],
    "catalog": {
      "foo": "^2.0.0"
    }
  },
  "overrides": {
    "quux": "npm:@myorg/quux@^1.0.0",
    "foo": "catalog:"
  }
}

Bun warns about a catalog: rule whose package is not in the catalog. A dependency that the rule applies to then fails to resolve.

A value of "$name" reuses the range you declared for name in your own dependencies, so "bar": "$foo" pins bar to whatever range you declared for foo.

Nested overrides#

You can scope a rule to one parent package, so it only applies to that package's direct dependency. Bun accepts the npm object form, the pnpm > form, and a parent with a version range:

package.json
{
  "name": "my-app",
  "overrides": {
    "micromatch": {
      ".": "^4.0.5",
      "picomatch": "^2.3.2"
    },
    "micromatch>picomatch": "^2.3.2",
    "micromatch@^4>picomatch": "^2.3.2"
  }
}

"." inside an object overrides micromatch itself, like a top-level "micromatch" rule.

"resolutions" accepts Yarn's path form. Bun accepts ** for compatibility, but either way the rule only affects the parent's direct dependency:

package.json
{
  "name": "my-app",
  "resolutions": {
    "micromatch/picomatch": "^2.3.2",
    "**/micromatch/**/picomatch": "^2.3.2"
  }
}

When several rules match, the most specific wins: parent-with-version > parent > top-level.

Version-scoped overrides#

The key can carry a version selector so the rule only applies to dependents whose declared range overlaps it. This is the form pnpm audit --fix writes:

package.json
{
  "name": "my-app",
  "overrides": {
    "semver@<7.5.2": "7.5.2",
    "webpack>terser@4": "4.8.1",
    "terser@4": {
      ".": "4.8.1"
    }
  }
}

Bun compares the selector with the range each dependent declares, not the resolved version. "semver@<7.5.2" applies to a dependent that declares ^7.3.0 (which could still pick 7.3.x) but not to one that declares ^7.5.2. Dependents using a dist-tag, workspace:, git, or URL specifier never match a selector.

bun add foo and bun update --latest write a range to package.json, and the selector is compared with that range. With "foo@>=2.0.0": "1.2.0" and 2.0.0 as the latest release, bun add foo writes "foo": "^2.0.0" and installs foo@1.2.0.

Two more selectors match a dependency by how it is declared, like Yarn's "resolutions":

  • "foo@catalog:" (or "foo@catalog:name") applies to dependents that declare foo with that catalog.
  • "my-foo@npm:foo@^1" applies to an npm: alias named my-foo that points at foo with an overlapping range. No other rule overrides an alias.

Limitations#

  • Bun supports only one parent level. It ignores a>b>c, a/b/c, and deeper object nesting with a warning.
  • Bun does not support pnpm's "pkg@" (empty selector) and "-" (remove dependency) forms, and skips them with a warning.
  • Bun writes a lockfile containing nested or version-scoped rules as lockfileVersion 3, which older versions of Bun cannot read.