Fix: npm ERR! Could not resolve peer dependency conflict
Part of: JavaScript & TypeScript Errors
Quick Answer
How to fix the npm ERR! Could not resolve peer dependency conflict error. Covers --legacy-peer-deps, --force, npm overrides, peerDependenciesMeta, React version conflicts, and debugging with npm ls.
The Error
You run npm install and get hit with this:
npm ERR! code ERESOLVE
npm ERR! ERESOLVE could not resolve
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/react
npm ERR! react@"^18.3.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.8.0 || ^17.0.0" from [email protected]
npm ERR! node_modules/react-beautiful-dnd
npm ERR! react-beautiful-dnd@"^13.1.1" from the root project
npm ERR!
npm ERR! Conflicting peer dependency: [email protected]
npm ERR! node_modules/react
npm ERR! peer react@"^16.8.0 || ^17.0.0" from [email protected]
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.The critical line is Could not resolve dependency. npm is telling you that two packages in your project demand different versions of the same dependency, and it cannot satisfy both at once.
This error blocks installation entirely. Nothing gets installed until you resolve it.
Why This Happens
Peer dependencies let a package declare that it expects the host project to provide a certain dependency at a compatible version. The package does not bundle it. Instead, it relies on whatever version you have installed.
A peer dependency conflict happens when two or more packages in your tree disagree on which version of that shared dependency is acceptable. Package A says it needs react@^17.0.0. Package B says it needs react@^18.0.0. There is no single version of React that satisfies both ranges.
npm 7+ Changed the Rules
Before npm 7, peer dependency conflicts were warnings. npm printed a message and installed everything anyway. Starting with npm 7, these conflicts became hard errors. If npm cannot build a valid dependency tree where every peer dependency requirement is met, installation fails.
This was an intentional decision. The old behavior led to silent runtime failures: packages would load incompatible versions of shared libraries, causing crashes that were extremely difficult to trace back to a version mismatch. The stricter behavior forces you to deal with conflicts at install time rather than at 2 AM in production.
Common Triggers
- Upgrading React (or another major framework) while third-party libraries still declare the old version as a peer dependency.
- Adding a new package that depends on a different major version of something already in your tree.
- Upgrading npm itself (or Node.js, which bundles npm). A project that installed fine under npm 6 may fail under npm 9 because the same conflicts were always there, just hidden.
- Stale lockfiles where
package-lock.jsonwas generated under different conditions and now contains resolution data that conflicts with current requirements.
If you are seeing the broader ERESOLVE unable to resolve dependency tree variant, the underlying cause is the same. See Fix: npm ERR! ERESOLVE unable to resolve dependency tree for additional context on that error message.
How Other Tools Handle This
The peer dependency conflict isn’t a JavaScript-language problem — it’s a package-manager-policy problem. Each tool ships with a different default stance, and switching tools is sometimes faster than fighting the resolver.
npm 7+ (strict). Treats peer-dependency violations as install-blocking errors, as you’ve already seen. Escape hatches: --legacy-peer-deps, --force, and overrides in package.json. Workspaces work but overrides only take effect at the root.
Yarn 1 / Classic (lenient). Prints peer-dependency warnings and continues. The override mechanism is the top-level resolutions field, which behaves similarly to npm overrides but is broader — it can pin any nested package to a specific version. Yarn 1 is in maintenance mode but still widely used.
Yarn Berry (v2+ — strict with escape hatches). Adds Plug’n’Play (no node_modules) and the packageExtensions field, which lets you patch a package’s manifest at install time — for example, adding a peer dependency to a library that forgot to declare one. Constraints (yarn constraints) let you enforce repo-wide policies like “every workspace must use the same React version.” If you’re in a large monorepo, this is the most powerful option.
pnpm (strict by default). Uses a content-addressable store and symlinks. Peer dependencies are resolved per-package, so it won’t silently flatten incompatible peers. Equivalents: pnpm.overrides in package.json, pnpm.peerDependencyRules.allowedVersions for fine-grained relaxation, and pnpm.peerDependencyRules.ignoreMissing to silence specific missing peers. Strict by default but very tunable. See pnpm peer dependency error troubleshooting if you’re already on pnpm and hitting the same wall.
Bun (lenient, npm-compatible). Bun’s package manager treats peer mismatches more like Yarn 1 — warnings rather than blocks — but reuses package.json overrides and works with existing package-lock.json files. The trade-off is the same as Yarn 1: faster installs, but it won’t catch real conflicts at install time.
Workspaces. All four (npm, Yarn Berry, pnpm, Bun) support workspaces, but their hoisting strategies differ. npm and Yarn 1 hoist aggressively; pnpm does not hoist at all (strict isolation); Yarn Berry with PnP eliminates hoisting entirely. Hoisting reduces disk use but is exactly what causes phantom dependencies and cross-workspace peer conflicts.
If you’ve spent more than an hour fighting npm’s resolver, try pnpm import && pnpm install to convert the lockfile and see if pnpm’s per-package resolution untangles the tree. If that still fails, the conflict is real and needs a version change — not a tool change.
Fix 1: Use the --legacy-peer-deps Flag
This tells npm to revert to npm 6 behavior and skip peer dependency resolution entirely:
npm install --legacy-peer-depsnpm will install all packages but will not attempt to ensure peer dependency ranges are satisfied. If a library says it needs react@^17.0.0 and you have React 18, npm will shrug and move on.
To make this permanent for your project, add it to .npmrc in your project root:
legacy-peer-deps=trueThis ensures every developer on the team (and your CI pipeline) uses the same behavior without remembering to pass the flag.
When this is safe: Most of the time. Peer dependency ranges are often overly conservative. A library that declares react@^17.0.0 frequently works fine with React 18 because React maintains strong backward compatibility. The peer dependency just has not been updated.
When this is risky: If the library genuinely uses APIs that were removed or changed in the newer version. In that case, you will get runtime errors instead of install-time errors.
Fix 2: Use the --force Flag
npm install --forceThis is more aggressive than --legacy-peer-deps. It forces npm to bypass all dependency resolution checks, overwrite existing packages if needed, and install everything regardless of conflicts.
Warning: --force can lead to duplicate copies of packages in your node_modules tree and unpredictable behavior. It is a last resort, not a first choice. Try --legacy-peer-deps first.
The key difference: --legacy-peer-deps ignores peer dependency requirements entirely. --force still tries to resolve them but will accept broken resolutions and push through. In practice, --force can cause npm to install multiple copies of the same package at different versions, which inflates your node_modules size and can cause subtle bugs with libraries that expect a single instance (like React).
Common Mistake: Using
--forcein CI pipelines as a permanent fix. This masks real dependency problems and can introduce non-deterministic builds. If you need a workaround in CI, prefer--legacy-peer-depsand document why.
Fix 3: Fix Version Ranges in package.json
This is the proper fix. Read the error output carefully. npm tells you exactly which packages conflict and what versions they expect.
Given an error like:
- Found:
[email protected] - Peer requires:
react@"^16.8.0 || ^17.0.0"from[email protected]
You have two paths:
Option A: Downgrade the shared dependency
If the conflicting library is critical and you are not using features exclusive to the newer version:
npm install react@17 react-dom@17This satisfies the peer dependency range. Make sure to also adjust any other packages tied to the major version (like react-dom, @types/react, etc.).
Option B: Upgrade the conflicting library
Check if a newer version of the library supports your dependency version:
npm view react-beautiful-dnd versions
npm view react-beautiful-dnd peerDependenciesIf the latest version has widened its peer dependency range to include your version, upgrade:
npm install react-beautiful-dnd@latestIf the library is unmaintained and stuck on old peer dependencies, consider switching to an actively maintained alternative. For example, react-beautiful-dnd has been largely superseded by @hello-pangea/dnd, which supports React 18 natively.
Fix 4: Use npm Overrides to Force Resolution
When you know a library works with a version outside its declared peer range, you can tell npm to override the resolution. Add an overrides field to your package.json:
{
"dependencies": {
"react": "^18.3.0",
"react-beautiful-dnd": "^13.1.1"
},
"overrides": {
"react-beautiful-dnd": {
"react": "$react"
}
}
}The $react reference means “use whatever version of react is specified in the top-level dependencies.” This forces react-beautiful-dnd to accept your React 18 installation.
For a broader override that applies across all packages:
{
"overrides": {
"react": "18.3.1"
}
}This pins react to a single version everywhere in the tree. Every package that depends on React will get 18.3.1, regardless of what its peer dependency range says.
After adding overrides, delete the lockfile and reinstall:
rm -rf node_modules package-lock.json
npm installNote: The overrides field requires npm 8.3 or later. Check your version with npm --version. If you are behind, upgrade with npm install -g npm@latest.
If you hit a 404 error while installing packages, that is a separate issue from peer dependency conflicts and usually points at registry configuration or scoped-package auth, not version ranges.
Fix 5: Update the Conflicting Packages
Before reaching for overrides or flags, check if simply updating your dependencies resolves the conflict. Outdated packages are the most common cause of peer dependency mismatches.
List outdated packages:
npm outdatedThis shows a table of packages with their current, wanted, and latest versions. Focus on the packages mentioned in the error output.
Update a specific package:
npm update some-libraryOr install the latest major version explicitly:
npm install some-library@latestIf multiple packages are outdated, update them together to avoid creating new conflicts:
npm updateAfter updating, run npm install again to verify the conflict is gone. Sometimes updating one package resolves the conflict, but reveals a new one with a different package. Work through them one at a time.
Pro Tip: Use
npm outdated --longto see the package type (dependency, devDependency) and homepage URL. The homepage often links to a changelog where you can quickly check if peer dependency ranges were updated in a recent release.
If your install script fails during the build step after resolving dependencies, that’s a different error — usually a postinstall script failing, not a resolution problem. Check the failing script’s stderr separately.
Fix 6: Use peerDependenciesMeta to Mark Peers as Optional
If you are a package author and your package has a peer dependency that is not strictly required, you can mark it as optional. This prevents the conflict from blocking installation for your users.
In your package’s package.json:
{
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0",
"react-native": ">=0.60"
},
"peerDependenciesMeta": {
"react-native": {
"optional": true
}
}
}With optional: true, npm will not throw an error if react-native is missing or at an incompatible version. It will still be used if present, but its absence will not block installation.
This is useful for libraries that support multiple environments (web and React Native, for example) where not every peer dependency applies to every user.
As a consumer, you cannot directly modify a library’s peerDependenciesMeta. But you can achieve a similar effect using overrides (Fix 4) or by patching the package with patch-package:
npx patch-package some-libraryThis creates a patch file that modifies the library’s package.json after installation. It is a more surgical approach than a global override.
Fix 7: Fix React Version Conflicts (React 17 vs 18)
React version mismatches are the single most common cause of peer dependency conflicts. This deserves its own section because the fix depends on the specific situation.
Scenario: You upgraded to React 18 and libraries broke
Many popular libraries released React 18 support months after React 18 launched. If you see errors like:
npm ERR! peer react@"^17.0.0" from [email protected]First, check if the library has a newer version with React 18 support:
npm view some-library peerDependencies --jsonIf the latest version still only supports React 17, check the library’s GitHub issues. There may be a beta or release candidate with React 18 support:
npm view some-library versions --jsonLook for versions tagged as next, beta, or rc.
Scenario: Multiple React-related packages conflict with each other
React ecosystem packages (react-dom, @types/react, react-test-renderer, @testing-library/react) all need to be on compatible versions. A common mistake is upgrading react but forgetting react-dom:
npm install react@18 react-dom@18For TypeScript projects, also align the type definitions:
npm install @types/react@18 @types/react-dom@18Scenario: You need React 17 and 18 in the same project
This is rare but happens in micro-frontend setups. npm does not support installing two versions of the same package at the top level. You would need to use package aliases:
npm install react-17@npm:react@17 react-18@npm:react@18This installs React 17 as react-17 and React 18 as react-18. You then configure each micro-frontend to import from the correct alias. This is advanced and introduces complexity. Only go down this path if you genuinely need both versions.
For missing file errors during React project setup, those are typically caused by an interrupted install — not a peer dependency conflict. Run rm -rf node_modules package-lock.json && npm install to rebuild cleanly.
Fix 8: Use npm ls to Debug the Dependency Tree
When the error output is not enough to understand the conflict, use npm ls to inspect the full dependency tree.
Show all instances of a specific package:
npm ls reactOutput looks like:
[email protected]
+-- [email protected]
+-- [email protected]
| `-- react@"^16.8.0 || ^17.0.0" (UNMET PEER DEPENDENCY)
+-- @testing-library/[email protected]
| `-- react@"^18.0.0" (peer satisfied)The UNMET PEER DEPENDENCY label pinpoints exactly where the conflict lives.
For a more detailed view:
npm ls react --allThis shows the entire tree including transitive dependencies. It is verbose but reveals deep conflicts that are not obvious from the top-level error.
To check the dependency tree before installing (useful for testing whether adding a new package would cause a conflict):
npm install some-new-package --dry-runThe --dry-run flag simulates the installation without writing anything to node_modules. If there is a peer dependency conflict, you will see the error without having modified your project.
You can also check what peer dependencies a package requires before installing it:
npm view some-package peerDependenciesThis outputs the peer dependency ranges so you can check compatibility before committing to the install.
For packages that seem to disappear from the registry entirely, that’s a deprecation or unpublish event — separate from peer dependency conflicts but worth resolving before you waste time on overrides for a package that’s been abandoned.
Still Not Working?
Monorepo Workspaces
In monorepo setups using npm workspaces, peer dependency resolution works differently. npm hoists dependencies to the root node_modules by default, but individual workspace packages may declare conflicting peer requirements.
Key rules for npm workspaces:
- Overrides must go in the root
package.json. Overrides declared in workspace packages are ignored. - Use
--workspaceto install into a specific workspace without affecting others:
npm install some-package --workspace=packages/my-app- If hoisting causes conflicts, you can prevent specific packages from being hoisted by adding a
.npmrcin the workspace package directory:
hoist=falsenpm-shrinkwrap.json Issues
If your project uses npm-shrinkwrap.json (a lockfile that ships with published packages), it can lock dependency resolutions to versions that conflict with your current setup. Unlike package-lock.json, shrinkwrap files are not ignored when your package is installed as a dependency.
Try removing the shrinkwrap and regenerating:
rm npm-shrinkwrap.json
npm install
npm shrinkwrapSwitching to Yarn or pnpm
If the npm resolution algorithm cannot find a valid tree for your dependencies, a different package manager might handle it. Both Yarn and pnpm resolve peer dependencies differently.
pnpm uses a strict content-addressable store and symlinks, which avoids many hoisting-related conflicts:
npx pnpm import # converts package-lock.json to pnpm-lock.yaml
npx pnpm installYarn (v1) uses resolutions instead of overrides:
{
"resolutions": {
"react": "18.3.1"
}
}Yarn Berry (v2+) has even more control through its Plug’n’Play resolution strategy, which eliminates node_modules entirely and can resolve conflicts that trip up npm.
Switching package managers is a significant change. Test thoroughly before committing, and make sure your CI pipeline and deployment scripts are updated. But if you have been fighting npm resolution errors for hours, it is worth trying.
Last Resort: Check the Debug Log
npm writes a detailed log file for every failed operation. The path is printed at the bottom of the error output:
npm ERR! A complete log of this run can be found in:
npm ERR! /home/user/.npm/_logs/2026-03-09T10_00_00_000Z-debug-0.logOpen that file. It contains the full resolution algorithm trace, showing every package npm tried to resolve and why it failed. Search for ERESOLVE in the log to jump to the relevant section. This often reveals transitive dependencies (dependencies of dependencies) causing the conflict, which are not visible in the summary error output.
Verify Node and npm Versions Match Your Project
A surprising number of “peer dependency” errors come from running an old node against a package.json that targets a newer Node. The engines field in package.json is advisory only — npm prints a warning but installs anyway. Run node --version and check it against the engines.node field. If you’re juggling multiple projects, use nvm use or volta to pin versions per directory. If you’re running into a missing-module crash after install rather than a resolution failure, the Node cannot find module troubleshooting guide covers the runtime side.
Check Your Registry and .npmrc Scopes
If you mix a public registry and a private one (GitHub Packages, Verdaccio, Nexus, JFrog Artifactory) via scoped .npmrc rules, peer dependency resolution can fail because npm cannot reach one of the registries — and the error surfaces as “could not resolve” rather than “registry not reachable.” Run npm config get registry and npm config get @yourscope:registry to verify both endpoints respond. Try curl -I $(npm config get registry) to confirm reachability.
Look for peerDependencies Declared as *
Some legacy packages declare "react": "*" as a peer dependency. With npm 7+, the * matches anything but also signals to the resolver that the package author never tested specific versions. If you see this in npm ls react --json, the package is likely abandoned. Either pin it via overrides, fork it, or replace it.
npm install Behaves Differently from npm ci
npm ci is stricter than npm install — it deletes node_modules and installs exactly what’s in package-lock.json. If npm install succeeds locally but npm ci fails in CI with a peer dependency conflict, your lockfile is out of sync with package.json. Regenerate it locally with rm package-lock.json && npm install, commit the new lockfile, and CI will pass.
Watch for Permission Errors Masquerading as Resolution Errors
If you installed a global package with sudo npm install -g in the past, npm may have left files owned by root inside ~/.npm or node_modules. The next non-sudo install fails partway through and can produce confusing peer-dependency-like errors because npm couldn’t write the lockfile cleanly. The npm EACCES permission denied guide explains how to reset ownership and avoid sudo for npm entirely.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: npm ERR! code E404 – Not Found (package not found in registry)
How to fix npm ERR code E404 not found error caused by typos, private registries, scoped packages, deleted packages, and authentication issues.
Fix: Deno PermissionDenied — Missing --allow-read, --allow-net, and Other Flags
How to fix Deno PermissionDenied (NotCapable in Deno 2) errors — the right permission flags, path-scoped permissions, deno.json permission sets, and the Deno.permissions API.
Fix: Fastify Not Working — 404, Plugin Encapsulation, and Schema Validation Errors
How to fix Fastify issues — route 404 from plugin encapsulation, reply already sent, FST_ERR_VALIDATION, request.body undefined, @fastify/cors, hooks not running, and TypeScript type inference.
Fix: Kafka Consumer Not Receiving Messages, Connection Refused, and Rebalancing Errors
How to fix Apache Kafka issues — consumer not receiving messages, auto.offset.reset, Docker advertised.listeners, max.poll.interval.ms rebalancing, MessageSizeTooLargeException, and KafkaJS errors.