
npm overrides: How to Force the Version of a Transitive Dependency
- VersionDude
- guides
- 7 min read
A vulnerable package three levels down, and a parent that has not released a fix. The overrides field in your root package.json lets you choose the version yourself, for the whole tree or for one parent only. It also has three rules that explain most of the cases where it appears to do nothing.
A vulnerability report names a package you never installed. It sits three levels down, pulled in by a dependency of a dependency, and the package in the middle has not published a release that moves past it. You cannot edit a `package.json` you do not own. What you can do is declare an `overrides` field in your own root `package.json` and tell npm which version to install instead.
The flat override: one version, everywhere in the tree

The shortest form names the package and the version. Writing `"overrides": { "foo": "1.2.4" }` makes npm install `foo` at 1.2.4 wherever it appears, no matter what version the packages that depend on it asked for. npm's documentation lists the three intended uses: replacing a version that has a known security issue, replacing a dependency with a fork, and making sure the same version of a package is used everywhere.
The value does not have to be an exact version. According to the documentation it can be any specifier npm accepts for a dependency: an exact version, a semver range, a dist-tag, or a replacement such as `npm:`, `file:` or a Git URL. A range is often the better choice for a security fix. `"foo": "^1.2.4"` enforces the patched release as a minimum and still lets later patches in, where an exact pin freezes the package until someone remembers to remove the line. If the difference between the two operators is hazy, caret vs tilde covers what each one allows.
Before writing the override, find out who is asking for the package. `npm explain foo` prints the chain of dependencies that causes `foo` to be installed, one block per copy in the tree, each one traced back to the root project. That output gives you the two facts you need: which parent is responsible, and whether one copy or several are involved. A flat override changes all of them, which is not always what you want.
Scoping the override to a single parent
Nest the key to limit the override to one branch of the tree. `"overrides": { "bar": { "foo": "1.2.4" } }` replaces `foo` only when it is a child of `bar`, or a grandchild, or anything deeper below it. Every other package that depends on `foo` keeps the version it resolved on its own. This is the form to reach for when one parent is stuck on a vulnerable release and the rest of the tree is fine.
- "foo": "1.2.4" : foo at 1.2.4 everywhere in the tree
- "bar": { "foo": "1.2.4" } : foo at 1.2.4 only below bar, at any depth
- "bar@2.0.0": { "foo": "1.2.4" } : only below that exact version of bar
- "foo": "$foo" : reuse the spec of your own direct dependency foo
- "foo": "npm:@scope/fork@1.2.4" : replace foo with another package
Keys can carry a version, and they can nest as deep as needed. `"bar@2.0.0": { "foo": "1.2.4" }` applies only when `foo` sits below that exact version of `bar`, so the override stops applying by itself the day `bar` is upgraded. Keys can be nested to any length to describe a longer path. And when you need to override a package and one of its children at the same time, the object form takes a `.` key for the package itself, next to the keys for its children.
A direct dependency is the one case npm refuses. You may not override a package you depend on directly unless the override and the dependency carry exactly the same spec; anything else stops the install with an `EOVERRIDE` error. The documented way around it is a reference: `"foo": "$foo"` tells npm to reuse whatever spec your own `dependencies` entry for `foo` declares, at every depth. Change the version in one place and the whole tree follows.
When the override seems to do nothing
Overrides are read from the root `package.json` only. npm's documentation is explicit: overrides in installed dependencies, workspaces included, are not considered when the tree is resolved. Two consequences follow. In a monorepo the field belongs in the root manifest, not in the package where the problem shows up. And an `overrides` block in a library you publish does nothing for the people who install it; the documentation points library authors to pinning the dependency or to `bundleDependencies` instead.
Then check the result instead of assuming it. Run `npm install` so the tree is resolved again, then run `npm explain foo` a second time: the version printed at the head of each block is the one now installed. Commit `package.json` and `package-lock.json` together, because the lockfile is what carries the resolved version to every other machine. package-lock.json vs package.json explains why the two files have to move as a pair.
An override installs a version the parent package never said it could work with. That is the whole point when the declared range only contains a vulnerable release, and it is also the risk: npm will not stop you from forcing a major version the parent was not written for. Treat the line as a patch with an expiry date. When the parent publishes a release that accepts the fixed version, delete the override.
Yarn and pnpm use a different field
The same idea exists in the other two package managers, under other names. Yarn reads a `resolutions` field, described in its manifest documentation as a way to use a specific resolution instead of anything the resolver would normally pick; its keys take one level of specificity, written `parent/child`. pnpm calls the setting `overrides` as npm does, places it in `pnpm-workspace.yaml` in its current documentation, and scopes it with a `>` separator, as in `bar@1>foo`. Both restrict the field to the root of the project, as npm does. The syntax does not transfer from one tool to the next, so a project that changes package manager has to rewrite the block.
The decision order is short. If the parent already has a release that accepts the fixed version, upgrade the parent and write no override at all. If it does not, scope the override to that parent instead of the whole tree. If the same package has to be identical everywhere, use the flat form, with a range when a minimum is enough. And if the install stops on a peer dependency conflict and not on a vulnerable version, the problem is a different one: peer dependencies covers it.
FAQ
Do npm overrides work in a workspace package.json?
No. npm only considers overrides declared in the root package.json of the project. Its documentation states that overrides in installed dependencies, workspaces included, are not considered in dependency tree resolution, so the field belongs in the root manifest.
What does the EOVERRIDE error mean?
It means an override targets a package you depend on directly with a spec that differs from the one in your dependencies. npm allows that override only when both specs are identical. Either align them, or write the override as a reference such as "foo": "$foo", which reuses the spec of your direct dependency.
Can an override use a range instead of an exact version?
Yes. The npm documentation says an override value can be any specifier npm accepts for a dependency: an exact version, a semver range, a dist-tag, or a replacement such as npm:, file: or a Git URL. A range like ^1.2.4 enforces a minimum patched release without freezing the package.
How do I find which package pulls in a transitive dependency?
Run npm explain followed by the package name. The command prints the chain of dependencies that causes the package to be installed, one block per copy present in the tree, each traced back to the root project.
Is npm overrides the same as Yarn resolutions?
The purpose is the same and the syntax is not. npm uses an overrides field with nested objects, Yarn uses a resolutions field with keys written parent/child, and pnpm uses an overrides setting with a > separator. All three restrict the field to the root of the project.



An override installs a version the parent package never said it could work with. That is the whole point when the declared range only contains a vulnerable release, and it is also the risk: npm will not stop you from forcing a major version the parent was not written for. Treat the line as a patch with an expiry date. When the parent publishes a release that accepts the fixed version, delete the override.