CoreMesh

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 ERESOLVE Unable to Resolve Dependency Tree: Safe Fixes

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):

text
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-lib wants 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:

text
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:

text
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):

text
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:

text
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:

text
npm install some-ui-lib@latest --dry-run

If you only want the lockfile updated and no downloads, use --package-lock-only:

text
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.

text
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:

text
legacy-peer-deps=true

The docs also list environment variables: anything starting with npm_config_ becomes a config value, with underscores instead of dashes:

text
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.

text
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.

text
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.

javascript
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-deps locally but CI runs plain npm install, CI will still fail. Put the setting in .npmrc or 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 --all before 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.json first, then package-lock.json, then yarn.lock. A stale lockfile can keep an old version in play.
  • Assuming the flag fixes the code. --legacy-peer-deps only 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:

  1. Read the Found and Could not resolve dependency lines.
  2. Try a version change on the stricter package.
  3. Preview with --dry-run if unsure.
  4. Use --legacy-peer-deps only after checking the library works.
  5. Record why in .npmrc or your README.
  6. Run npm ls --all and 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.

Saved you an hour? Send a tip.

USDC or ETH on Base. Keeps the notes free and the examples running.

Base
0x9dFD2Ee86182f92464b50809C6702B999a0Cd54f
Open in wallet · 5 USDC

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

`code` and ``` blocks work

No account needed. Comments are read before they show up, usually the same day. Your email is never asked for.

Indra Wijaya

Builds and runs AI agents, bots and content pipelines, and writes down what works and what broke.

About · Email · RSS

Keep reading

New notes, no spam.

One post at a time, only when there's something worth reading. Follow in any feed reader.

Subscribe via RSS