Fixing npm EBADENGINE 'Unsupported Engine' Warnings
npm warns EBADENGINE Unsupported engine because an installed package requires a Node or npm version different from yours. This page shows what triggers the warning, when it is safe to ignore, and how to make engine constraints deterministic.
Exact symptoms and error messages
npm warn EBADENGINE Unsupported engine {
npm warn EBADENGINE package: 'some-lib@3.0.0',
npm warn EBADENGINE required: { node: '>=20' },
npm warn EBADENGINE current: { node: 'v18.19.0' }
npm warn EBADENGINE }
Root cause analysis
A package's engines field declares the runtime it supports. npm compares it to your active Node/npm and warns (or errors, under engine-strict) on a mismatch. The warning is advisory by default, but it signals real drift between developer and CI environments — the same determinism problem the packageManager field and lockfile strategies address, and it ties back to Dependency Resolution Explained.
EBADENGINE is npm comparing an installed package's declared engines against the runtime you are actually using. By default it is a warning, because a minor mismatch is often harmless — but it is a real signal that a dependency was authored for a runtime you are not running, and the incompatibility it warns about can be anything from a missing built-in API to different default behavior. The warning is advisory; the drift it reveals is not.
The deeper issue is environment inconsistency. If developers run Node 18, CI runs Node 20, and a dependency requires Node 20, the warning appears on some machines and not others, and a genuine incompatibility can hide until it hits the one environment that matters. Pinning the runtime everywhere converts 'works on my machine' into a single, enforced version, which is the same determinism goal behind the packageManager field and frozen lockfile installs.
Resolution and configuration patch
Pin the runtime so every environment matches, and decide whether to enforce:
// package.json
{
"engines": { "node": ">=20.0.0" },
"packageManager": "pnpm@10.4.1"
}
# .npmrc — turn the warning into a hard error if you require the engine
engine-strict=true
Add an .nvmrc (or Volta pin) so local shells select the matching Node.
CLI validation and debug commands
# Confirm your active runtime against the requirement
node --version && npm --version
# See which dependency demands the newer engine
npm ls some-lib
# Fail fast on mismatch
npm install --engine-strict
Prevention and CI guardrails
- Pin Node with
enginesplus an.nvmrc/Volta so shells match CI. - Pin the package manager via
packageManagerand Corepack. - Set
engine-strict=truewhen a wrong runtime would genuinely break the build. - Fail CI early with a Node-version check step rather than tolerating the warning.
Pinning the runtime across every environment
The durable fix is to make one Node version authoritative and select it everywhere the code runs. Declare the floor in engines, pin the exact version for local shells with an .nvmrc or Volta, and set the same version in CI. When all three agree, EBADENGINE either disappears or becomes a deliberate, universal signal rather than a per-machine surprise.
{
"engines": { "node": ">=20.0.0" },
"volta": { "node": "20.11.1" }
}
With Volta (or an .nvmrc plus nvm use), a developer's shell selects the pinned Node automatically on entering the project, and the CI job installs the same version. The dependency's engines requirement is then satisfied identically everywhere, so the warning reflects a real decision about your supported runtime rather than an accident of who ran the install.
Deciding whether to enforce with engine-strict
By default EBADENGINE is advisory, which is usually right — you do not want a trivial mismatch to block every install. But when a wrong runtime would genuinely break your build or ship broken behavior, promote the warning to an error so the mismatch cannot slip through.
# .npmrc
engine-strict=true
With engine-strict, an unsupported engine fails the install outright. Reserve it for cases where the engine requirement is load-bearing — a dependency that uses a Node API added in a specific version, say. For everything else, a CI step that checks node --version against your declared floor gives you an early, explicit failure without making local installs brittle. The decision is really about how costly a wrong runtime is: enforce when the cost is a broken build, warn when it is merely untidy.
Frequently Asked Questions
Is EBADENGINE safe to ignore?
Often, for a minor mismatch — it is a warning, not a failure. But it means you are running an unsupported runtime, so treat it as a signal to align environments before it becomes a real incompatibility.
How do I make the warning a hard error?
Set engine-strict=true in .npmrc, or run npm install --engine-strict. Then an unsupported engine blocks the install instead of merely warning.
Is EBADENGINE safe to ignore?
For a minor mismatch, often — it is a warning, not a failure. But it means you are running a runtime the dependency was not authored for, so treat it as a prompt to align environments before the incompatibility becomes real.
How do I make the same Node version run everywhere?
Declare the floor in engines, pin the exact version with .nvmrc or Volta for local shells, and set the identical version in CI. When all three agree, the warning reflects a real decision rather than a per-machine accident.
When should I turn on engine-strict?
When a wrong runtime would genuinely break the build — for instance a dependency that uses a Node API from a specific version. For softer cases, a CI node --version check fails early without making local installs brittle.
Related
- Dependency Resolution Explained — how ranges and the graph decide installed versions.