| |

npm 26 ๐ŸŒ Overrides and Peer Dependencies (overrides, peerDependencies)

โฑ๏ธ Estimated read: 10 min | |

Two fields in package.json control dependency resolution when the default behavior does not produce the tree the project needs. peerDependencies declares what a package expects its consumer to provide, and peerDependenciesMeta marks specific peers as optional. overrides forces a specific version of a package anywhere in the dependency tree, including transitive dependencies that the project does not directly control. The first field is a contract between a library and its consumer; the second is a consumer-side intervention that rewrites the resolved graph.

Key point: overrides does not resolve incompatibility; it rewrites the version number that the resolution algorithm compares. A package that declares a peer dependency on React 17 will accept React 19 after an override, but the override does not verify that the package’s internals actually work with React 19. The override moves the discovery of the mismatch from install time to runtime .


Why these fields exist

The peer contract problem. A plugin or library that extends another packageโ€”a React component library, a Babel preset, an ESLint pluginโ€”does not bundle its own copy of the host package. It declares a peerDependencies entry to say “I expect the consumer to provide this package, and it must be within this version range.” npm 7 and later enforce this check by default, producing an ERESOLVE error when the consumer’s version is outside the declared range . The error is not a bug; it is the resolution algorithm reporting that a package’s claim about the host version is false.

The optional peer problem. Some peer dependencies are genuinely optional. A logging library might integrate with chalk if the consumer has it installed, but it should not fail if chalk is absent. peerDependenciesMeta with optional: true expresses this: if the package is present, it must satisfy the version range; if it is absent, the install succeeds without warning . The optional flag does not relax the version constraintโ€”it only removes the requirement that the package exist .

The transitive fix problem. A vulnerable package may be nested several levels deep in the dependency tree, and the parent package that pulls it in may not have released a version that accepts the fixed version. overrides forces the resolution: the vulnerable version is replaced with the safe version anywhere it appears in the tree, regardless of what the parent’s package.json declares . The override is a claim that the parent works with a version its maintainers never tested.


a. peerDependencies and peerDependenciesMeta

A peerDependencies entry is a version range that the consumer must satisfy. It is not installed by the package that declares it; it is checked against the consumer’s installed version. npm 7 and later auto-install peer dependencies if they are missing, which changed the behavior from the npm 3โ€“6 era where peers were only checked if present .

{
  "name": "my-plugin",
  "peerDependencies": {
    "react": "^18.0.0 || ^19.0.0"
  }
}

The peerDependenciesMeta field marks specific peers as optional. The key must match a key in peerDependencies, and the value is an object with an optional boolean.

{
  "name": "my-logger",
  "peerDependencies": {
    "chalk": "^5.0.0"
  },
  "peerDependenciesMeta": {
    "chalk": {
      "optional": true
    }
  }
}

The behavior is: if chalk is not installed, the install succeeds. If chalk is installed but at version 4, the install produces an ERESOLVE error because the version does not satisfy ^5.0.0. The optional flag removes the requirement that the package exist, not the requirement that its version be compatible . A package that uses an optional peer should guard its require or import in a try/catch block and check the version at runtime, because the peer may be absent .

The strict-peer-deps configuration option changes the failure behavior. When set to true, any conflicting peer dependency is treated as an install failure, even if npm could resolve it by using a nearest non-peer dependency. The default is false, which means npm resolves deep peer conflicts using the nearest non-peer specification and prints a warning .


b. overrides: Forcing Transitive Versions

The overrides field replaces a package anywhere in the dependency tree. It can be scoped to a specific parent, a specific version, or the entire tree.

{
  "overrides": {
    "semver": "7.5.2"
  }
}

This forces every semver in the tree to 7.5.2, regardless of what the parents declare. A nested form scopes the override to a specific parent chain:

{
  "overrides": {
    "@some/cli": {
      "semver": "7.5.2"
    }
  }
}

This forces semver to 7.5.2 only when it is reached through @some/cli. Other paths that resolve semver are unaffected . A version-specific form overrides only when the resolved version matches:

{
  "overrides": {
    "ejs@2.7.4": "3.1.10"
  }
}

This replaces ejs@2.7.4 with 3.1.10 but leaves ejs@4.0.0 untouched . The $package syntax references the version declared in the root dependencies, which is useful when the override should track the root’s choice:

{
  "overrides": {
    "react": "$react"
  }
}

This forces every react in the tree to match the root’s react version .

The override is a blunt instrument. It bypasses the parent’s semver range, which means the parent’s maintainers never tested the combination. A major-version jump forced through an override is a last resort, and the full test suite should be run afterward . An override that is no longer needed should be removed when the parent releases a version that accepts the fixed dependency; a stale override quietly pins the project to yesterday’s “fixed” version, which eventually becomes the vulnerable one.


c. The Interaction Between the Two

overrides can affect peer dependency resolution. When an override forces a version that satisfies a peer range, the ERESOLVE error disappears. When it forces a version outside the peer range, the error may still occur, or npm may print a warning depending on the strict-peer-deps setting.

A case that is documented in the community is using overrides to satisfy a peer dependency that the parent has not updated. A package declares a peer on bar@^1.0.0, and the consumer has bar@1.1.0-pre.1. The pre-release is not within the ^1.0.0 range according to npm’s semver implementation, so the install fails. An override that points bar to the pre-release version removes the failure, but it also disables the peer dependency validity check for that package completely . The override does not make the pre-release compatible with the parent; it only removes the check that would have caught the incompatibility.

The interaction is the reason overrides is considered debt. It is effective for unblocking an install or fixing a transitive vulnerability, but it is not a substitute for the parent package updating its own dependencies. The override should be documented with a comment or a README note explaining why it exists and when it can be removed.


Complete Example Session

// ============================================
// PART 1: PEER DEPENDENCY DECLARATION
// ============================================
{
  "name": "my-react-plugin",
  "peerDependencies": {
    "react": "^18.0.0 || ^19.0.0"
  }
}


// ============================================
// PART 2: OPTIONAL PEER
// ============================================
{
  "name": "my-logger",
  "peerDependencies": {
    "chalk": "^5.0.0"
  },
  "peerDependenciesMeta": {
    "chalk": {
      "optional": true
    }
  }
}


// ============================================
// PART 3: OVERRIDE A TRANSITIVE VULNERABILITY
// ============================================
{
  "overrides": {
    "semver": "7.5.2"
  }
}


// ============================================
// PART 4: SCOPED OVERRIDE
// ============================================
{
  "overrides": {
    "@some/cli": {
      "semver": "7.5.2"
    }
  }
}


// ============================================
// PART 5: VERSION-SPECIFIC OVERRIDE
// ============================================
{
  "overrides": {
    "ejs@2.7.4": "3.1.10"
  }
}


// ============================================
// PART 6: REFERENCE THE ROOT DEPENDENCY
// ============================================
{
  "dependencies": {
    "react": "^19.0.0"
  },
  "overrides": {
    "react": "$react"
  }
}


// ============================================
// PART 7: VERIFY THE RESOLUTION
// ============================================
// npm ls semver
// npm explain semver
// npm ls --all


// ============================================
// PART 8: REMOVE THE OVERRIDE WHEN POSSIBLE
// ============================================
// Check whether the parent has released a version that accepts the fix
// npm view @some/cli dependencies.semver
// If the parent now accepts 7.5.2, remove the override

The eight parts trace the peer declaration, the optional peer, the override forms, and the verification. The critical distinction is between the peer contract (Part 1) and the consumer-side override (Part 3): the first is a declaration by the package, the second is an intervention by the consumer.


Quick Reference

peerDependencies vs. overrides

AspectpeerDependenciesoverrides
Declared byLibrary authorConsumer
PurposeState the host version rangeForce a resolved version
Installed bynpm 7+ (auto-install)npm 8.3+
Failure modeERESOLVE errorRuntime incompatibility

peerDependenciesMeta

FieldEffect
optional: truePackage may be absent
Version constraintStill enforced if present

overrides Forms

FormScope
"pkg": "1.2.3"Entire tree
"parent": { "pkg": "1.2.3" }Only under parent
"pkg@1.0.0": "1.2.3"Only when resolved version is 1.0.0
"pkg": "$pkg"Match root dependency

Best Practices

โœ… Do This:

// Mark truly optional peers with peerDependenciesMeta
"peerDependenciesMeta": { "chalk": { "optional": true } }              // โœ…
// Scope overrides to the smallest necessary subtree
"overrides": { "@some/cli": { "semver": "7.5.2" } }                    // โœ…
# Verify the resolved version after the override
npm ls semver                                                          // โœ…

โŒ Don’t Do This:

// Override without documenting why
"overrides": { "react": "19.0.0" }  // why? when can it be removed?     // โŒ
// Override a major version without testing
"overrides": { "some-pkg": "3.0.0" }  // parent tested 2.x only         // โŒ
// Use optional: true to relax the version constraint
// It only makes the package optional, not the version                     // โŒ

Common Pitfalls

PitfallWhy It HappensFix
ERESOLVE after overrideOverride version outside peer rangeAdjust override or use --legacy-peer-deps
Optional peer still errorsVersion present but incompatibleRemove or upgrade the peer
Override not appliednpm < 8.3Upgrade npm
Runtime failure after overrideParent incompatible with forced versionRun tests; consider forking
Stale overrideParent updatedRemove override, verify

Real-World Examples

1. Peer Range

"peerDependencies": { "react": "^18.0.0 || ^19.0.0" }

2. Optional Peer

"peerDependenciesMeta": { "chalk": { "optional": true } }

3. Global Override

"overrides": { "semver": "7.5.2" }

4. Scoped Override

"overrides": { "@some/cli": { "semver": "7.5.2" } }

5. Version-Specific Override

"overrides": { "ejs@2.7.4": "3.1.10" }

6. Root Reference

"overrides": { "react": "$react" }

7. Verify Resolution

npm ls semver

8. Explain the Chain

npm explain semver

9. Strict Peer Deps

npm install --strict-peer-deps

10. Legacy Mode

npm install --legacy-peer-deps

Visual

Peer Dependency Check

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  CONSUMER                     PLUGIN                         โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”          โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”           โ”‚
โ”‚  โ”‚ react@19.0.0    โ”‚          โ”‚ peerDependenciesโ”‚           โ”‚
โ”‚  โ”‚                 โ”‚          โ”‚ react: ^18||^19 โ”‚           โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜           โ”‚
โ”‚           โ”‚                            โ”‚                     โ”‚
โ”‚           โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                     โ”‚
โ”‚                      โ–ผ                                       โ”‚
โ”‚              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                             โ”‚
โ”‚              โ”‚ 19 satisfies    โ”‚                             โ”‚
โ”‚              โ”‚ range?          โ”‚                             โ”‚
โ”‚              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                             โ”‚
โ”‚                       โ”‚                                      โ”‚
โ”‚                 โ”Œโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”                                โ”‚
โ”‚                 โ–ผ           โ–ผ                                โ”‚
โ”‚              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”                             โ”‚
โ”‚              โ”‚ YES  โ”‚   โ”‚ NO   โ”‚                             โ”‚
โ”‚              โ”‚      โ”‚   โ”‚      โ”‚                             โ”‚
โ”‚              โ”‚ OK   โ”‚   โ”‚ERESOLVEโ”‚                           โ”‚
โ”‚              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                             โ”‚
โ”‚                                                              โ”‚
โ”‚  The peer is a check, not an installation.                   โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Override Mechanism

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  DEPENDENCY TREE                                             โ”‚
โ”‚  app โ†’ @some/cli@2.1.0 โ†’ semver@7.3.5 (vulnerable)           โ”‚
โ”‚                                                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚
โ”‚  โ”‚ overrides: { "semver": "7.5.2" }                        โ”‚โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚
โ”‚                             โ–ผ                                โ”‚
โ”‚  app โ†’ @some/cli@2.1.0 โ†’ semver@7.5.2 (fixed)               โ”‚
โ”‚                                                              โ”‚
โ”‚  The override rewrites the resolved version.                 โ”‚
โ”‚  It does not change @some/cli's declared range.              โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Optional Peer

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  peerDependencies: { "chalk": "^5.0.0" }                     โ”‚
โ”‚  peerDependenciesMeta: { "chalk": { "optional": true } }     โ”‚
โ”‚                                                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                        โ”‚
โ”‚  โ”‚ chalk present?  โ”‚โ”€โ”€โ”€โ”€โ–บ NO: install succeeds              โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                        โ”‚
โ”‚           โ”‚ YES                                              โ”‚
โ”‚           โ–ผ                                                  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                        โ”‚
โ”‚  โ”‚ version ^5?     โ”‚โ”€โ”€โ”€โ”€โ–บ NO: ERESOLVE                      โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                        โ”‚
โ”‚           โ”‚ YES                                              โ”‚
โ”‚           โ–ผ                                                  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                        โ”‚
โ”‚  โ”‚ OK              โ”‚                                        โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                        โ”‚
โ”‚                                                              โ”‚
โ”‚  optional means absent is OK; present must be compatible.    โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
peerDependenciesHost version range declared by a library
peerDependenciesMeta{ "optional": true } for optional peers
Optional peer absentInstall succeeds
Optional peer incompatibleERESOLVE
overridesForce a resolved version anywhere in the tree
Nested overrideScope to a specific parent
Version-specific override"pkg@1.0.0": "2.0.0"
Root reference override"pkg": "$pkg"
Override verificationnpm ls <pkg>, npm explain <pkg>

Key takeaways:

  • peerDependencies is a contract, not an installation. The library declares what it expects the consumer to provide. npm 7+ enforces the check by default, producing ERESOLVE when the consumer’s version is outside the range. The check exists because a plugin that reaches into the host package’s internals is only safe against the versions it was built for .
  • peerDependenciesMeta makes a peer optional without relaxing its version. If the package is absent, the install succeeds. If it is present but incompatible, the install fails. The optional flag removes the requirement that the package exist, not the requirement that its version be compatible .
  • overrides forces a resolved version, bypassing the parent’s semver range. It is the correct tool for fixing a vulnerable transitive dependency when the parent has not released a version that accepts the fix. It is a claim that the parent works with a version its maintainers never tested, and the full test suite should be run after applying it .
  • Override scoping limits the blast radius. A global override affects every path in the tree. A nested override ({ "parent": { "pkg": "1.2.3" } }) affects only the path through that parent. A version-specific override ("pkg@1.0.0": "1.2.3") affects only when the resolved version matches. The smallest scope that fixes the problem is the correct scope .
  • Overrides are debt. They should be documented, reviewed when the parent releases a new version, and removed once the parent accepts the fixed dependency. A stale override quietly pins the project to a version that may itself become vulnerable .

Remember: peerDependencies and overrides solve different problems at different layers. The peer dependency is the library author’s statement about what the consumer must provide. The override is the consumer’s statement that the resolved graph must contain a specific version, regardless of what the libraries declare. The first is a contract; the second is an intervention. When the contract is satisfied, no intervention is needed. When the contract cannot be satisfied because the parent package is unmaintained or slow to release, the override is the mechanism that unblocks the install or fixes the vulnerability. The override does not make the parent compatible; it only removes the check that would have caught the incompatibility. That is why the override requires testing, documentation, and eventual removal.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!