How Do I Resolve npm and Yarn Dependency Conflicts?
How do I resolve npm and yarn dependency conflicts?
TL;DR
- Bottom line: Most npm/yarn dependency conflicts are caused by incompatible peer
dependency versions. The proper fix is to align versions via
npm ls,npm explain, and targeted upgrades. Useoverrides(npm) orresolutions(yarn) for transitive dependency pinning.--legacy-peer-depsand--forceare escape hatches, not solutions. - Key tool/command:
npm explain <package>to trace the conflict chain, then eithernpm updatethe parent or add"overrides"to package.json. - Watch out for: Using
--forceor--legacy-peer-depsglobally — they hide real incompatibilities that surface as runtime errors. Never setlegacy-peer-deps=truein global.npmrc. - Works with: npm 7+ (ERESOLVE enforcement), npm 8.3+ (overrides), Yarn Classic v1 (resolutions), Yarn Berry v2+ (resolutions), pnpm 7+ (strict peer mode).
Constraints
- Never recommend
--forceor--legacy-peer-depsas a first solution — always diagnose the conflict first withnpm lsandnpm explain. [src6] overridesonly works in the root package.json — nested/workspace overrides are ignored by npm. [src1, src5]- Yarn
resolutionsmust be in the root package.json for workspaces — inner package resolutions are ignored for hoisted dependencies. [src3] - After any override/resolution change, delete
node_modulesand lockfile, then reinstall. Stale lockfiles are the #1 cause of "override not working" reports. [src5] - pnpm does NOT auto-install conflicting peer dependencies — it prints a warning and skips them. You must manually choose a version. [src4]
npm overridesrequires npm >= 8.3.0 — older versions silently ignore theoverridesfield. [src2, src5]
Quick Reference
| # | Error / Symptom | Likelihood | Diagnostic Command | Fix |
|---|---|---|---|---|
| 1 | ERESOLVE unable to resolve dependency tree |
~45% | npm install 2>&1 | head -50 |
Read conflict, align versions or add overrides [src6] |
| 2 | Could not resolve dependency: peer X@^Y from Z |
~25% | npm explain <package> |
Upgrade Z to version supporting current X [src7] |
| 3 | npm WARN ERESOLVE overriding peer dependency |
~10% | npm ls <package> |
Review if override is intentional; may need overrides [src1]
|
| 4 | Invalid peer dependency after --legacy-peer-deps |
~8% | npx check-peer-dependencies |
Remove flag, fix root cause with version alignment [src6] |
| 5 | Yarn warning ... has unmet peer dependency |
~5% | yarn why <package> |
Add resolutions to package.json [src3]
|
| 6 | pnpm WARN ... requires peer but none installed |
~4% | pnpm why <package> |
Install missing peer or use pnpm.peerDependencyRules [src4] |
| 7 | Duplicate packages in bundle | ~3% | npm dedupe --dry-run |
Run npm dedupe or add overrides [src1]
|
| 8 | npm ci fails but npm install works |
Common | Check lockfile freshness | Regenerate: rm package-lock.json && npm install [src6] |
Decision Tree
START — npm/yarn install fails with dependency conflict
├── Which package manager?
│ ├── npm 7+
│ │ ├── Error says "ERESOLVE unable to resolve dependency tree"?
│ │ │ ├── YES → Run `npm explain <conflicting-package>` [src6]
│ │ │ │ ├── Can you upgrade the parent package?
│ │ │ │ │ ├── YES → `npm install <parent>@latest`
│ │ │ │ │ └── NO → Add `overrides` to package.json [src1, src5]
│ │ │ │ └── Is this a dev-only tool (linter, test runner)?
│ │ │ │ ├── YES → `--legacy-peer-deps` is acceptable [src6]
│ │ │ │ └── NO → Fix with overrides
│ │ │ └── NO → Check `npm ls` for version conflicts
│ │ └── Error says "peer dep ... from ..."?
│ │ └── `npm explain <package>` → upgrade or override [src7]
│ ├── Yarn Classic (v1) or Yarn Berry (v2+)
│ │ └── Add `resolutions` to root package.json → `yarn install` [src3]
│ └── pnpm
│ └── Install missing peer or configure peerDependencyRules [src4]
└── Still failing?
└── Nuclear: rm -rf node_modules package-lock.json && npm cache clean --force && npm install [src6]
Decision Logic
If the install error contains ERESOLVE unable to resolve dependency tree
Run npm explain <conflicting-package> to identify the parent and its peer requirement. Upgrade the parent to a version that supports the current peer if possible. [src6, src7]
If no upgrade path exists for the conflicting parent package
Add an overrides (npm ≥ 8.3) or resolutions (yarn / pnpm) block in the root package.json pinning the desired version, then delete node_modules + lockfile and reinstall. [src1, src3, src5]
If the conflict is in a dev-only tool (linter, test runner, formatter)
--legacy-peer-deps is an acceptable short-term workaround because the package is not shipped at runtime. Still prefer a proper override for reproducibility. [src6]
If running pnpm and seeing requires peer ... but none was installed
pnpm does not auto-install conflicting peers. Either install the missing peer explicitly or configure pnpm.peerDependencyRules.allowedVersions / ignoreMissing in package.json. [src4]
If npm ci fails on CI but npm install works locally
The lockfile is out of sync with overrides or package.json. Locally run rm -rf node_modules package-lock.json && npm install, commit the regenerated lockfile, then rerun CI. [src5, src6]
If you must use --force to install
Treat this as a diagnostic signal, not a fix. --force bypasses cache, checksum AND peer checks — strictly weaker than --legacy-peer-deps. Find the real conflict via npm explain and add a proper override before merging. [src6]
If the project uses workspaces (npm / yarn / pnpm monorepo)
Place overrides / resolutions in the ROOT package.json only. Workspace-member overrides are ignored for hoisted dependencies. After editing, do a full clean reinstall from the workspace root. [src1, src3]
Step-by-Step Guide
1. Identify the conflicting packages
Run npm install and read the ERESOLVE output carefully. It tells you exactly which packages
conflict. [src6]
# See the full error (npm truncates by default)
npm install 2>&1 | head -80
# Or use verbose mode for maximum detail
npm install --verbose 2>&1 | grep -A 5 "ERESOLVE"
Verify: The error shows While resolving: [email protected] and
Could not resolve dependency: peer react@"^17.0.0" from [email protected].
2. Understand why each version is required
Use npm explain (alias: npm why) to trace the dependency chain. [src5,
src6]
# Show which packages depend on react and what versions they require
npm explain react
# Tree view of all installed versions
npm ls react
# Check for all outdated packages
npm outdated
Verify: npm explain react shows each dependant with its required version range.
3. Attempt version alignment (preferred fix)
Update the package that has the outdated peer dependency requirement. [src7]
# Update a specific package to its latest version
npm install @testing-library/react@latest
# Update all packages to latest compatible versions
npm update
# Check what would change without modifying anything
npm update --dry-run
Verify: npm ls react shows a single version with no UNMET PEER
DEPENDENCY warnings.
4. Use overrides for npm (when alignment is impossible)
When a transitive dependency pins an incompatible version and no update is available. [src1, src5]
{
"dependencies": {
"some-library": "^2.0.0"
},
"overrides": {
"react": "^18.2.0",
"some-library": {
"old-subdep": "2.0.0"
}
}
}
# After adding overrides, always clean install
rm -rf node_modules package-lock.json
npm install
# Verify the override took effect
npm ls old-subdep
npm explain old-subdep
Verify: npm ls old-subdep shows only the overridden version.
5. Use resolutions for Yarn
Yarn Classic and Berry both support resolutions in package.json. [src3]
{
"dependencies": {
"some-library": "^2.0.0"
},
"resolutions": {
"react": "18.2.0",
"**/lodash": "4.17.21",
"some-library/old-subdep": "2.0.0"
}
}
# Clean install after changing resolutions
rm -rf node_modules yarn.lock
yarn install
# Verify
yarn why react
6. Configure pnpm peer dependency rules
pnpm provides granular control via pnpm.peerDependencyRules in package.json. [src4]
{
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": ["@babel/core"],
"allowedVersions": {
"react": "18"
},
"allowAny": ["eslint"]
},
"overrides": {
"lodash": "4.17.21"
}
}
}
# Clean install
rm -rf node_modules pnpm-lock.yaml
pnpm install
# Verify
pnpm why react
Code Examples
package.json: npm overrides for React peer conflict
// Input: Library requires react@^17 but project uses react@18
// Output: Forces all packages to accept [email protected]
{
"name": "my-app",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"legacy-component-lib": "^3.0.0"
},
"overrides": {
"react": "$react",
"react-dom": "$react-dom"
}
}
// Note: "$react" references the version in your own dependencies.
// Requires npm >= 8.3.0. [src1, src2]
.npmrc: project-level configuration for persistent flags
# Input: Project that must use --legacy-peer-deps on every install
# Output: Flag applied automatically without passing it each time
# .npmrc (place in project root, commit to git)
legacy-peer-deps=true
# Prefer deduplication when resolving (npm 8.13+)
prefer-dedupe=true
# Strict engine checks
engine-strict=true
Bash: automated dependency conflict diagnosis
#!/bin/bash
# Input: Run in project root with package.json
# Output: Conflict report with fix suggestions
echo "=== npm Dependency Conflict Diagnosis ==="
echo "npm version: $(npm --version)"
echo "node version: $(node --version)"
echo ""
echo "--- Peer dependency check ---"
npx check-peer-dependencies 2>/dev/null || echo "(install: npm i -g check-peer-dependencies)"
echo ""
echo "--- Duplicate packages ---"
npm ls --all 2>&1 | grep "deduped" | sort | uniq -c | sort -rn | head -10
echo ""
echo "--- Outdated packages ---"
npm outdated --long 2>/dev/null | head -20
echo ""
echo "--- Install dry run (conflicts) ---"
npm install --dry-run 2>&1 | grep -E "(ERESOLVE|peer|conflict|WARN)" | head -20
Anti-Patterns
Wrong: Using --force or --legacy-peer-deps as default
# BAD — hides real incompatibilities that cause runtime errors [src6, src7]
npm install --force
# or
npm config set legacy-peer-deps true # global — affects ALL projects!
# Silences the error without fixing the conflict.
Correct: Diagnose first, then fix the root cause
# GOOD — understand the conflict, then fix it [src5, src6]
npm explain react # see why each version is needed
npm install some-lib@latest # update the conflicting package
# If no update available: add "overrides" to package.json
Wrong: Deleting package-lock.json on every conflict
# BAD — loses reproducibility, may introduce NEW conflicts [src6]
rm package-lock.json
npm install
# Different versions resolved every time. CI becomes non-deterministic.
Correct: Targeted lockfile regeneration
# GOOD — only regenerate when you've actually changed overrides/versions
rm -rf node_modules package-lock.json
npm install
git add package-lock.json && git commit -m "Regenerate lockfile after override change"
Wrong: Wildcard overrides without version constraint
// BAD — "*" means any version, including breaking majors [src1, src5]
{
"overrides": {
"lodash": "*"
}
}
Correct: Pin to specific version or range
// GOOD — explicit version, predictable behavior [src1, src5]
{
"overrides": {
"lodash": "4.17.21"
}
}
Common Pitfalls
--legacy-peer-depsmasks runtime crashes: The flag installs packages that declare they need React 17 alongside your React 18. The package may call React 17-only APIs and crash at runtime. Always test thoroughly after using this flag. [src6]- npm
overridessilently ignored on old npm: Theoverridesfield requires npm >= 8.3.0. Older versions skip it with no warning. CI runners often have stale npm versions. [src2, src5] - Yarn
resolutionsignored in workspace inner packages: Resolutions must be in the root package.json. Aresolutionsfield in a workspace member's package.json is ignored for hoisted dependencies. [src3] - Stale lockfile after override change: Adding
overrideswithout deletingnode_modulesand the lockfile means the override is not applied. Alwaysrm -rf node_modules package-lock.json && npm install. [src5] - pnpm strict mode rejects all peer mismatches: pnpm does not auto-install conflicting
peers. Use
pnpm.peerDependencyRules.allowedVersionsfor intentional mismatches orignoreMissingfor optional peers. [src4] npm ciignores overrides not in lockfile:npm ciinstalls from lockfile only. Changedoverridesrequire regeneratingpackage-lock.jsonfirst vianpm install. [src5, src6]
Diagnostic Commands
# === Identify conflicts ===
npm ls --all 2>&1 | head -100 # full dependency tree
npm explain <package-name> # trace dependency chain (alias: npm why)
npm outdated --long # list all outdated packages
npm install --dry-run 2>&1 | grep -E "ERESOLVE|peer|conflict"
# === Yarn equivalents ===
yarn why <package-name>
yarn list --pattern <package-name>
# === pnpm equivalents ===
pnpm why <package-name>
pnpm ls <package-name>
# === Fix commands ===
npm dedupe # reduce duplicate versions
npm dedupe --dry-run # preview changes
rm -rf node_modules package-lock.json # clean slate
npm cache clean --force # clear npm cache
npm install # fresh install
npx check-peer-dependencies # verify peer deps satisfied
# === Version checks ===
npm --version # need >= 8.3 for overrides
node --version # check engine compatibility
Version History & Compatibility
| Version / Tool | Change | Impact |
|---|---|---|
| npm 6.x | Peer deps NOT auto-installed, warnings only | No ERESOLVE errors; conflicts were silent [src6] |
| npm 7.0 (Oct 2020) | Peer deps auto-installed, ERESOLVE introduced | Breaking — many projects hit new errors [src6] |
| npm 8.3 (Dec 2021) | overrides field added |
Proper fix for transitive dependency conflicts [src1, src2] |
| npm 8.13 (Jun 2022) | --prefer-dedupe flag added |
Reduces duplicate versions during install |
| npm 9.x (Oct 2022) | Lockfile v3, stricter resolution | Better conflict detection |
| npm 10.x (Sep 2023) | Current stable, ships with Node 20+ | overrides fully stable, improved error messages |
| Yarn Classic 1.x | resolutions always supported |
Simple version pinning [src3] |
| Yarn Berry 2-4 | resolutions + strict peer mode |
Stricter than npm; Plug'n'Play changes resolution [src3] |
| pnpm 7+ | Strict peer deps, peerDependencyRules |
Does not auto-install conflicting peers [src4] |
| pnpm 9.x (2024) | Current stable, enhanced overrides |
pnpm.overrides for version pinning [src4] |
When to Use / When Not to Use
| Use When | Don't Use When | Use Instead |
|---|---|---|
npm install fails with ERESOLVE |
Package is fundamentally incompatible with your stack | Switch to an alternative package |
| Need to pin a transitive dependency version | You can upgrade the direct dependency to resolve the conflict | npm update <package> |
| Security patch for nested dependency | Override would change major version with breaking API | Wait for upstream fix or fork |
| CI builds fail on peer dep warnings | --legacy-peer-deps is already set globally |
Remove global flag, fix per-project |
| Monorepo with shared dependencies | Each workspace needs a different version | Use workspace-specific dependency ranges |
Important Caveats
- npm
overridesvs yarnresolutionsare NOT interchangeable: npm usesoverrides(npm >= 8.3), Yarn usesresolutions. Including both is allowed but each tool only reads its own field. [src1, src3] --forceand--legacy-peer-depsdo different things:--forcebypasses ALL safety checks (cache, checksums, peer deps).--legacy-peer-depsONLY reverts peer dep behavior to npm 6 style. Always prefer--legacy-peer-depsif you must use a flag. [src6]- Overrides apply to the entire dependency tree: An override like
"react": "^18.2.0"forces EVERY package in the tree to use React 18.2+, even packages not tested with it. This can cause subtle runtime bugs. [src1, src5] - pnpm resolves peers differently than npm/yarn: pnpm may create multiple copies of the same package version, each with different peer dependency sets. This is by design but can cause confusion when inspecting node_modules. [src4]
- Lock files are package-manager specific:
package-lock.json(npm),yarn.lock(Yarn),pnpm-lock.yaml(pnpm) are NOT interchangeable. Mixing package managers causes phantom conflicts. Enforce one viaenginesorcorepack. [src6]