npm --legacy-peer-deps: installation success is not a compatible peer tree
By ON Lab · Sources checked November 8, 2026
--legacy-peer-deps tells npm to ignore peerDependencies when constructing the package tree. It can make an installation proceed without making the selected versions compatible. --force has a broader set of effects and is not an interchangeable compatibility fix. First identify the conflicting peer contract, then choose compatible versions or a deliberately reviewed temporary exception.
A historical reader asked how these flags differ when recreating node_modules with npm ci. We checked current npm 11 documentation and built an offline fixture from three original local tarballs. It demonstrates a failed normal installation, a successful bypass with an invalid peer graph, a compatible installation, and the effect of matching the legacy setting during ci.
Read the relationship that npm is rejecting
A package's peerDependencies express a compatibility relationship with a host package. A plugin might require a particular range of the framework supplied by its user. The relevant question is whether the installed host satisfies that range, not whether both package names are present on disk.
Read the full error: the package being resolved, the host version found, the package declaring the peer, and the required range. Reinstalling Node or clearing a proxy does not change an incompatible declared range. A separate error such as an unavailable package version needs its own diagnosis; bypassing peer checks cannot make a nonexistent release appear.
node --version
npm --version
npm ls --all
npm explain HOST_PACKAGE
Use the actual host package name in the last command. These are inspection steps, not instructions to replace dependencies. Keep the original package.json, lockfile and relevant error output before experimenting in a separate checkout. Do not delete a lockfile merely to remove the immediate error message.
What the local fixture proved
We made a tiny host at version 1.0.0. One invented plugin required host ^2.0.0; another plugin version required host ^1.0.0. The package names below describe our fixture, not real packages to download. Tests used Node 24.22.2 and npm 11.19.1, offline mode, original local tarballs, and disabled lifecycle scripts.
| Selected graph | Install result | Full dependency inspection |
|---|---|---|
| Host 1 with plugin requiring host 2 | Normal install rejected it with ERESOLVE | No valid install claimed |
| Same incompatible graph with legacy-peer-deps | Install exited successfully | npm ls reported an invalid peer relationship and exited nonzero |
| Host 1 with plugin requiring host 1 | Normal install succeeded | npm ls exited successfully |
The bypass changed enforcement, not the plugin's declared requirement. Conversely, the compatible metadata did not prove a real application worked: our packages contained only original minimal modules and no framework integration. A graph check and an application test are separate evidence.
Why force is a different choice
The current configuration documentation describes force as removing several protections. Allowing conflicting root peer dependencies is one documented effect among others. It is not accurately summarized as “whichever dependency downloads last wins,” nor does it simply mean to refresh an old cached file.
Legacy peer handling has a narrower stated purpose: ignoring peer contracts during tree construction. It also differs from omit=peer, which can avoid unpacking peers while still designing a tree that accommodates them. Our local fixture exercised legacy handling, not force or every configuration combination.
Do not choose the broadest flag just because the error lists it. If an exception is necessary, document the specific compatibility evidence, affected integration, intended duration and removal condition. The error disappearing is not that evidence.
Keep npm ci's construction settings consistent
The npm ci documentation warns that tree-shaping settings used to create a lockfile may also be needed when consuming it. We tested this with the lockfile created by the fixture's legacy installation:
- Ordinary offline npm ci rejected the incompatible peer relationship.
- The same operation with legacy-peer-deps enabled exited successfully.
This explains why “it installed on my workstation” does not establish that an unconfigured deployment command will reproduce it. Record the npm version and effective project policy alongside the reviewed lock. npm ci itself does not repair package metadata or rewrite the lockfile to solve a conflict.
If the team intentionally retains a legacy policy, keep that decision reproducible in the project's supported configuration or CI command. Do not silently put the setting into a global user configuration that changes unrelated projects. Reproducing the bypass is still different from validating the unsupported combination.
Prefer a reviewed compatible set
- Identify the host, plugin and exact conflicting ranges.
- Check their current maintainer compatibility guidance and available releases.
- Choose an aligned set in an isolated checkout, preserving the original lock.
- Regenerate the lock through the intended supported installation workflow.
- Run a clean ci install with the same intended settings.
- Inspect the dependency graph and run the actual build, tests and affected feature.
The right correction may be upgrading a plugin, choosing a compatible host, replacing a discontinued integration or correcting an overly restrictive declaration with maintainer evidence. The original historical framework versions were not installed here, so this guide does not name a particular Angular version as a verified fix.
Completion means more than a zero exit code
Keep three outcomes separate: installation completed, the declared graph is consistent, and the application behaves correctly. Our bypass passed only the first. Our compatible fixture passed the first two in its tiny graph. Neither observation certifies a real deployment.
Checked November 8, 2026. Tests used local fabricated packages with no registry downloads or lifecycle scripts. The original application, force behavior, network setup and production runtime were not reproduced. Historical questions do not establish current traffic or advertising revenue.