npm ERESOLVE Unable to Resolve Dependency Tree: Safe Fixes
Fix the npm ERESOLVE unable to resolve dependency tree error: what the peer conflict means, safe fixes in order, and when legacy-peer-deps is fine.

npm error code ERESOLVE means two packages in your project ask for incompatible versions of the same peer dependency, and npm refuses to guess. Nothing is broken on disk. npm stopped before installing anything because it couldn't build a tree that satisfies every peerDependencies range.
The safest fix is to change a version so the ranges agree, usually by upgrading or downgrading the package named in the error. If you can't, npm install --legacy-peer-deps is a reasonable way forward in many cases, as long as you understand what it turns off. The sections below go from safest to last resort.
The error you're seeing#
The exact text varies by package, but it looks like this (older npm versions print npm ERR! instead of npm error):
npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: [email protected]
npm error Found: [email protected]
npm error node_modules/react
npm error react@"^18.2.0" from the root project
npm error
npm error Could not resolve dependency:
npm error peer react@"^16.8.0 || ^17.0.0" from [email protected]
npm error node_modules/some-ui-lib
npm error some-ui-lib@"^2.3.0" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry
npm error this command with --force or --legacy-peer-deps
npm error to accept an incorrect (and potentially broken) dependency resolution.
The same shape shows up for React, Angular, or anything else. Only the package names change.
What the peer dependency conflict means#
A peer dependency is a package saying: I work with this other package, but you install it, and it must be in this version range. In the example above, some-ui-lib declares a peer range for React that stops at 17, while your project has React 18.
Read the message in three parts:
- Found: what is already in your tree (
[email protected]). - Could not resolve dependency: the package whose peer range doesn't match (
some-ui-libwants React 16 or 17). - While resolving: the package whose install triggered the check. This is often your root project, but it can be a nested dependency.
npm 7 and later check peer dependencies and fail on a conflict. npm versions 3 through 6 ignored them, which is why a project that installed fine years ago can suddenly throw ERESOLVE after you upgrade Node or npm. The legacy-peer-deps setting exists to bring back the old behavior.
By default npm can still resolve conflicts that are deep in the graph by using the nearest non-peer dependency specification, printing a warning. The hard failure happens when the conflict touches your own direct dependencies, or when strict-peer-deps is set to true.
Fix 1: Install a version that matches#
This is the safe fix and the one I'd try first. Look at the "Could not resolve dependency" block and decide which side to move.
Upgrade the package that is too strict. Often a newer release of the library has widened its peer range:
npm install some-ui-lib@latest
You can also ask for a specific version or range. Quote ranges so your shell treats them as one argument:
npm install [email protected]
npm install some-ui-lib@">=2.4.0 <4"
Or move your own package. If the library has no release that supports your version, pin the shared package to a version both sides accept (for example React 17 in the example above):
npm install react@17 react-dom@17
That is a bigger decision, because it changes your app's runtime. Only do it if you're ready to run it on that version.
Confirm it worked. Run the install again, then list the tree:
npm install
npm ls --all
If the install finishes with no ERESOLVE and npm ls --all shows no invalid or missing peer entries, you're done. Commit the updated package.json and package-lock.json together.
Fix 2: Preview the change with a dry run#
When you aren't sure what an upgrade will pull in, ask npm to report what it would do without touching node_modules:
npm install some-ui-lib@latest --dry-run
If you only want the lockfile updated and no downloads, use --package-lock-only:
npm install some-ui-lib@latest --package-lock-only
These are handy for trying a few version combinations quickly. If the dry run still prints ERESOLVE, that combination won't work and you can move on to the next one without cleaning up anything.
The same applies to npm update. If an update triggers ERESOLVE, one of the new versions has narrowed or shifted a peer range, so the fix is the same: pick versions whose ranges overlap.
Fix 3: Use --legacy-peer-deps#
This flag makes npm completely ignore peerDependencies when building the tree, as npm versions 3 through 6 did.
npm install --legacy-peer-deps
The docs describe it as a way to move forward when overly strict peer dependencies collide. They also say its use is not recommended, because it won't enforce the peer dependency contract that other packages may rely on.
In practice, I think it's fine when all of these are true:
- The peer range is simply out of date. The library works with your version, but the maintainers haven't bumped the range yet.
- You've run your tests or at least started the app and exercised the parts that use that library.
- You accept that npm will no longer warn you when a real incompatibility appears later.
It's a poor choice when the library actually breaks on your version, or when you're using it just to make the red text go away without checking.
Make it stick for the project. If everyone on the team needs it, put it in the per-project .npmrc file at the root of the project:
legacy-peer-deps=true
The docs also list environment variables: anything starting with npm_config_ becomes a config value, with underscores instead of dashes:
npm_config_legacy_peer_deps=true npm install
This helps in CI where you don't control the command line. Be aware that a committed .npmrc hides every future peer conflict for the whole team, so I'd add a comment in your README on why it's there and revisit it when the library updates.
Confirm it worked. The install completes, and npm config ls -l shows legacy-peer-deps as true if you set it through a file or environment variable.
How it differs from --omit=peer#
They sound similar but aren't. --omit=peer avoids unpacking peer dependencies on disk, but npm still designs a tree where they could be placed correctly. --legacy-peer-deps skips peer dependencies entirely during tree building. Use neither as a general cure for ERESOLVE.
Last resort: --force#
Warning: read this before using it. --force removes various protections against side effects, common mistakes and malicious input. One of the things it does is allow conflicting peerDependencies to be installed in the root project. The docs say that if you don't have a clear idea of what you want to do, you should not use this option.
npm install --force
It does far more than skip a peer check, so I'd treat it as a temporary experiment on a throwaway branch. Prefer --legacy-peer-deps over --force when the only problem is a peer range, because it has a narrower effect. If you do use --force, don't commit the result without running your test suite.
Making npm stricter instead#
Sometimes you want the opposite. Setting strict-peer-deps to true (while --legacy-peer-deps is not set) turns every conflicting peer dependency into an install failure, even those npm could reasonably resolve on its own.
npm install --strict-peer-deps
If a teammate sees ERESOLVE and you don't, check whether one of you has this set in a user-level or project .npmrc. npm config ls -l prints the effective values. The config sources are, in order of priority: environment variables, then the npmrc files (project, user, global, and npm's built-in one), then defaults.
Test your own range check#
This small script mimics the core idea of a peer range check, using only major versions. It's a simplification of how semver works, but it makes the conflict in the error message easy to see.
function satisfies(version, range) {
const major = Number(version.split('.')[0]);
return range.split('||').some((part) => {
const m = part.trim().match(/^\^(\d+)\./);
return m && Number(m[1]) === major;
});
}
const installed = { react: '18.2.0' };
const peers = [
{ pkg: 'some-ui-lib', dep: 'react', range: '^16.8.0 || ^17.0.0' },
{ pkg: 'other-lib', dep: 'react', range: '^17.0.0 || ^18.0.0' },
];
for (const p of peers) {
const ok = satisfies(installed[p.dep], p.range);
console.log(`${p.pkg} wants ${p.dep}@${p.range}: ${ok ? 'ok' : 'CONFLICT'} with ${installed[p.dep]}`);
}
Real semver handles many more range forms, so don't use this for anything beyond understanding the message.
Common mistakes and gotchas#
- Reaching for --force first. It disables more than you need. Try a version change, then
--legacy-peer-deps. - Fixing it on your machine only. If you run
npm install --legacy-peer-depslocally but CI runs plainnpm install, CI will still fail. Put the setting in.npmrcor an environment variable so both agree. - Ignoring the Found line. Sometimes the real culprit is a package you forgot you had. Check what is installed with
npm ls --allbefore changing versions. - Mixing lockfile managers. If the package has a package-lock, a shrinkwrap file, or a yarn lock file, npm installs by that, with
npm-shrinkwrap.jsonfirst, thenpackage-lock.json, thenyarn.lock. A stale lockfile can keep an old version in play. - Assuming the flag fixes the code.
--legacy-peer-depsonly changes how npm builds the tree. If the library truly doesn't support your React or Angular version, it can still crash at runtime. - Install order surprises. The npm algorithm is deterministic, but different trees can come out if dependencies are requested in a different order, so a conflict may show up only after adding one specific package.
Quick reference#
| Situation | What to run |
|---|---|
| Library has a newer release | npm install some-ui-lib@latest |
| Need a specific version | npm install [email protected] |
| Want to preview first | npm install some-ui-lib@latest --dry-run |
| Peer range outdated but code works | npm install --legacy-peer-deps |
| Same, for the whole team | legacy-peer-deps=true in project .npmrc |
| Same, in CI without editing files | npm_config_legacy_peer_deps=true |
| Want npm stricter | npm install --strict-peer-deps |
| Nothing else works, throwaway branch | npm install --force |
Checklist:
- Read the Found and Could not resolve dependency lines.
- Try a version change on the stricter package.
- Preview with
--dry-runif unsure. - Use
--legacy-peer-depsonly after checking the library works. - Record why in
.npmrcor your README. - Run
npm ls --alland your tests before committing the lockfile.
For the full option descriptions, see the npm config docs and the npm install docs.
Quick answers
What does npm ERESOLVE unable to resolve dependency tree mean?
Two packages in your tree require incompatible versions of the same peer dependency, so npm can't build a valid tree and stops before installing. Change a version so the ranges overlap, or opt out of peer checks.
Is npm install --legacy-peer-deps safe?
It's acceptable when a peer range is merely out of date and you've tested that the library works. The docs don't recommend it, because npm stops enforcing the peer dependency contract and won't warn you about real conflicts.
What's the difference between --force and --legacy-peer-deps?
--legacy-peer-deps ignores peerDependencies when building the tree. --force removes a wider set of protections, including allowing conflicting peerDependencies in the root project, so it's riskier.
Why does npm update or npm install fail with ERESOLVE on an old project?
npm 7 and later check peer dependencies, while npm 3 through 6 ignored them. An old project can therefore hit conflicts that were never reported before, and picking compatible versions usually fixes it.
Did this solve it?
One tap. No account.
Sorry about that. What was missing?
from 0 readers
Saved you an hour? Send a tip.
USDC or ETH on Base. Keeps the notes free and the examples running.
0x9dFD2Ee86182f92464b50809C6702B999a0Cd54f
On a computer? Scan the code with your phone wallet, or copy the address.
USDC or ETH on Base works best. Other tokens on Base, Ethereum or Arbitrum also reach this address. Tips are optional and unlock nothing; every guide stays free.
Discussion
Start the discussion
Next note