Node ERR_REQUIRE_ESM "require() of ES Module" in CI - Fix the Interop
By Kaveh Alemi·Latchkey
ERR_REQUIRE_ESM means CommonJS code called require() on a package that only ships an ES module. On older Node, require cannot load ESM synchronously.
What this error means
A node script throws Error [ERR_REQUIRE_ESM]: require() of ES Module ... not supported, usually after upgrading a dependency that went ESM-only.
node
Error [ERR_REQUIRE_ESM]: require() of ES Module
/home/runner/work/app/app/node_modules/chalk/source/index.js
from /home/runner/work/app/app/src/log.js not supported.
Instead change the require of index.js to a dynamic import().
Diagnose it: the shell in CI is not your shell
Package scripts run under a different shell, a different PATH, and a non-interactive environment on a runner. Most scripts that fail only in CI are relying on something the login shell gave them locally: a tool on PATH, an environment variable from a dotfile, or a TTY.
Terminal
# what the script can actually see
npm run env | grep -E "^(PATH|NODE_ENV|CI)="
# is the binary on PATH for the script, not just for you?
npm exec -- which <tool> || echo "not resolvable from npm scripts"
# run the exact script with tracing
sh -x -c "$(node -p "require('./package.json').scripts.build")"
Common causes
A dependency dropped CommonJS support
The package now publishes ESM only, so a require() of it can no longer load synchronously on Node versions before the require(esm) support.
A CommonJS caller importing an ESM-only module
Your entry point is CommonJS but it pulls in a module that exposes no CommonJS entry.
How to fix it
Use a dynamic import
Replace the require() with an await import() inside an async function.
Read the default export off the resolved module namespace.
JavaScript
const { default: chalk } = await import('chalk');
Pin a CommonJS-compatible version
If converting to ESM is not feasible, pin the last release that shipped CommonJS.
Schedule the ESM migration separately.
Terminal
npm install chalk@4
Make failures fail the job
A multi-command script can report success while a middle command failed, which produces the worst kind of CI result: a green build that shipped something broken.
.github/workflows/ci.yml
# pipefail is NOT set by default in every runner shell- name:Buildshell:bashrun:|set -euo pipefailnpm run build | tee build.log
How to prevent it
Track which dependencies have gone ESM-only, plan a deliberate move of the consuming package to ESM, and avoid mixing synchronous require with ESM-only modules.
Frequently asked questions
What causes Node ERR_REQUIRE_ESM "require() of ES Module" in CI?
There are 2 common causes: a dependency dropped commonjs support and a commonjs caller importing an esm-only module. The package now publishes ESM only, so a require() of it can no longer load synchronously on Node versions before the require(esm) support.
How do I fix Node ERR_REQUIRE_ESM "require() of ES Module" in CI?
There are 2 fixes depending on which cause you have: use a dynamic import and pin a commonjs-compatible version. Work through them in order, since the first is the most common.
What does Node ERR_REQUIRE_ESM "require() of ES Module" in CI actually mean?
A node script throws Error [ERR_REQUIRE_ESM]: require() of ES Module ...
How do I stop Node ERR_REQUIRE_ESM "require() of ES Module" in CI happening again?
Track which dependencies have gone ESM-only, plan a deliberate move of the consuming package to ESM, and avoid mixing synchronous require with ESM-only modules.