Back to core workflows Fix dependency resolution Tune package metadata Jump to monorepo patterns

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

Exact symptoms and error messages Exact symptoms and error messages in production JavaScript package workflows. Exact symptoms and error messages Exact symptoms and error messages in production JavaScript package workflows.
Exact symptoms and error messages — the core idea of this section at a glance.
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.

Root cause analysis A package's engines field declares the runtime it supports. Root cause analysis A package's engines field declares the runtime it supports.
Root cause analysis — the core idea of this section at a glance.

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:

Resolution and configuration patch Pin the runtime so every environment matches, and decide whether to enforce: Resolution and configuration patch Pin the runtime so every environment matches, and decide whether to enforce:
Resolution and configuration patch — the core idea of this section at a glance.
// 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

CLI validation and debug commands CLI validation and debug commands in production JavaScript package workflows. CLI validation and debug commands CLI validation and debug commands in production JavaScript package workflows.
CLI validation and debug commands — the core idea of this section at a glance.
# 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 engines plus an .nvmrc/Volta so shells match CI.
  • Pin the package manager via packageManager and Corepack.
  • Set engine-strict=true when a wrong runtime would genuinely break the build.
  • Fail CI early with a Node-version check step rather than tolerating the warning.
Prevention and CI guardrails Prevention and CI guardrails in production JavaScript package workflows. Prevention and CI guardrails Prevention and CI guardrails in production JavaScript package workflows.
Prevention and CI guardrails — the core idea of this section at a glance.

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.

Runtime pinning engines floor plus a shell pin plus a CI pin agree everywhere. engines floor >= supported version .nvmrc / Volta local shell pin CI node-version same version dependency satisfied warning gone
One authoritative Node version across engines, local shells, and CI removes drift.
{
  "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.

Enforce or warn Whether to promote EBADENGINE to a hard error. How costly is a wrong runtime? breaks build engine-strict wrong behavior CI version check cosmetic leave advisory
Enforce when a wrong runtime breaks the build; warn when it is merely untidy.
# .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