ERR_REQUIRE_ESM when require() is used to load a package that is a pure ES module. The fastest fix is replacing require('pkg') with const { default: pkg } = await import('pkg'), or pinning to the last CommonJS version of the package (e.g. npm install chalk@4).
Exact Error Messages
Node.js produces one of these two error strings depending on the context:
Error [ERR_REQUIRE_ESM]: require() of ES Module
/project/node_modules/chalk/source/index.js
from /project/app.js not supported.
chalk is an ES module and cannot be required.
Instead use dynamic import():
const chalk = await import('chalk');
at Object.load (node:internal/modules/cjs/loader:1277:19)
at Function.Module._resolveFilename (node:internal/modules/cjs/loader:1039:15)
at Function.Module._load (node:internal/modules/cjs/loader:885:27)
...
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module:
/project/node_modules/some-pkg/index.mjs
require() of ES modules is not supported.
require() of /project/node_modules/some-pkg/index.mjs
from /project/app.js is an ES module file as it is a .mjs file.
Instead change the require of index.mjs in /project/app.js to a dynamic import()
which is available in all CommonJS modules.
Node.js helpfully includes the exact import() fix right in the error message. The package name in the stack trace is your starting point.
What is ERR_REQUIRE_ESM?
ERR_REQUIRE_ESM is thrown when the
CommonJS loader
encounters a package or file that is a pure ES module — one that uses
import/export syntax and cannot be loaded synchronously.
require() is synchronous by design; ES modules are inherently asynchronous,
so Node.js forbids mixing them this way.
Starting in late 2020, many widely-used npm packages dropped their CommonJS builds and became ESM-only, making this one of the most frequently encountered Node.js errors in 2023–2026.
All Causes
| Cause | What triggers it | Recommended fix |
|---|---|---|
| ESM-only npm package | require('chalk') after upgrading to chalk v5+ |
Dynamic import() or pin to last CJS version |
.mjs file extension |
require('./helper.mjs') |
Rename to .js and use import, or use import() |
Package has "type": "module" |
Any require() of a package whose package.json contains "type": "module" |
Dynamic import() or pin to last CJS version |
| Transitive dependency upgrade | Your dependency requires foo; foo is now ESM-only after an npm update |
Pin the intermediate dep or update it to a version compatible with ESM foo |
| TypeScript targeting CommonJS | TypeScript compiles import pkg from 'pkg' to const pkg = require('pkg') when module: commonjs in tsconfig |
Change module to nodenext or downgrade the package |
| Jest test runner | Jest uses CommonJS by default; importing an ESM-only package in a test file fails | Pin to last CJS version, or enable Jest ESM mode, or switch to Vitest |
| Electron main process | Electron's main process uses CommonJS; ESM-only packages cannot be required | Use import() dynamically or pin to last CJS package version |
Auto-requiring .mjs in a loader |
Build tools or custom require hooks that auto-load files with .mjs extension |
Use import() in the loader instead of require() |
Root Cause in package.json
A package is a pure ES module when its package.json contains
"type": "module", or when it ships only .mjs files with no
CommonJS fallback. From that point on, require() cannot load it.
// node_modules/chalk/package.json (v5+)
{
"name": "chalk",
"type": "module" // ← makes the entire package ESM-only
}
// A package with conditional exports but no "require" condition is also ESM-only:
{
"exports": {
".": {
"import": "./index.mjs"
// no "require" key → require() will fail
}
}
}
Fix 1 – Use dynamic import() (recommended for CJS projects)
Replace require() with import(). This works in any CommonJS file
— no project-wide changes needed.
// Before (broken)
const chalk = require('chalk');
// After – dynamic import returns a Promise
const { default: chalk } = await import('chalk');
// If you are not in an async context, wrap in an async IIFE:
(async () => {
const { default: chalk } = await import('chalk');
console.log(chalk.green('Hello'));
})();
// Useful pattern: lazy-load once and reuse
let _chalk;
async function getChalk() {
if (!_chalk) _chalk = (await import('chalk')).default;
return _chalk;
}
{ default: chalk } destructuring to get the default export.
For named exports: const { red, green } = await import('chalk').
For node-fetch: const fetch = (...args) => import('node-fetch').then(({ default: f }) => f(...args))
Fix 2 – Convert your project to ESM
If your codebase is entirely your own, converting to ESM is the cleanest long-term solution.
// 1. Add to your package.json:
{ "type": "module" }
// 2. Change require() to import:
import chalk from 'chalk';
import fetch from 'node-fetch';
// 3. Change module.exports to export:
export function myFunction() { ... }
export default myClass;
Note: converting to ESM disables __dirname and __filename — use import.meta.url with the URL constructor as a replacement.
import { fileURLToPath } from 'url';
import path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const __filename = fileURLToPath(import.meta.url);
import { helper } from './utils.js' — not './utils'.
Fix 3 – Pin to the last CJS version
The easiest zero-code-change fix. Install the last version of the package that still shipped a CommonJS build.
| Package | Last CJS version | First ESM-only version | Install command |
|---|---|---|---|
chalk | 4.1.2 | v5 | npm install chalk@4 |
node-fetch | 2.6.7 | v3 | npm install node-fetch@2 |
nanoid | 3.x | v4 | npm install nanoid@3 |
got | 11.8.3 | v12 | npm install got@11 |
execa | 5.x | v6 | npm install execa@5 |
p-map | 4.x | v5 | npm install p-map@4 |
ora | 5.x | v6 | npm install ora@5 |
open | 8.x | v9 | npm install open@8 |
figures | 3.x | v5 | npm install figures@3 |
find-up | 5.x | v6 | npm install find-up@5 |
p-limit | 3.x | v5 | npm install p-limit@3 |
slash | 3.x | v5 | npm install slash@3 |
gulp-imagemin | 7.1.0 | v8 | npm install --save-dev gulp-imagemin@7 |
Fix 4 – TypeScript-specific fix
TypeScript compiles import pkg from 'pkg' to const pkg = require('pkg')
when "module": "commonjs" is set in tsconfig.json. This causes
ERR_REQUIRE_ESM at runtime even though your TypeScript source looks correct.
Option A: Downgrade the package (quickest for TypeScript projects) — install
the last CJS version as shown in Fix 3, plus its @types package if needed:
npm install node-fetch@2
npm install --save-dev @types/node-fetch@2.x
Option B: Change the TypeScript module target
// tsconfig.json
{
"compilerOptions": {
"module": "nodenext", // or "es2020"
"moduleResolution": "nodenext",
"esModuleInterop": true,
"target": "es2020"
}
}
Option C: Use dynamic import() in TypeScript
// Works in TypeScript with any module target
const { default: chalk } = await import('chalk');
Fix 5 – Use createRequire() inside an ESM file
If you have converted your project to ESM but still need to load a CommonJS module
(the reverse problem), use createRequire:
// In an .mjs file or a file in an ESM package:
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
// Now you can require() CJS modules from an ESM context:
const someOldCjsPackage = require('some-old-cjs-pkg');
Fix 6 – Use .cjs file extension
If your project has "type": "module" in package.json but one
specific file must remain CommonJS, rename it with a .cjs extension.
Node.js always treats .cjs files as CommonJS regardless of the type field.
# Rename the file:
mv server.js server.cjs
# Now require() works inside server.cjs even when "type": "module" is set:
const express = require('express'); // fine in .cjs
Fix 7 – CJS-compatible alternatives
For some packages, a maintained CJS-compatible alternative exists:
node-fetch→ use the built-infetch(Node.js 18+) oraxioschalk→ usekleurorcolorette(both ship CJS builds)nanoid→ useuuidorcrypto.randomUUID()(Node.js 14.17+)got→ useaxiosor the built-inhttpsmoduleexeca→ usechild_process.execSyncfor simple cases
Fixing ERR_REQUIRE_ESM in Jest
Jest uses CommonJS by default, so importing an ESM-only package in a test fails immediately. You have several options:
Option A: Pin to the last CJS version (simplest)
npm install chalk@4 # Last CJS version of chalk
Option B: Enable Jest's experimental ESM mode
// package.json
{
"type": "module",
"scripts": {
"test": "NODE_OPTIONS=--experimental-vm-modules jest"
}
}
// jest.config.js (renamed to jest.config.mjs or with "type":"module")
export default {
transform: {} // disable transform — let Node handle ESM natively
};
Option C: Transform with Babel
// Install: npm install --save-dev babel-jest @babel/preset-env
// babel.config.js
module.exports = {
presets: [['@babel/preset-env', { targets: { node: 'current' } }]]
};
Option D: Switch to Vitest (ESM-native, Jest-compatible API)
npm install --save-dev vitest
# Then replace jest with vitest in scripts
Fixing ERR_REQUIRE_ESM in Electron
Electron's main process runs as CommonJS by default. ESM-only npm packages cannot be
directly require()d. Use a dynamic import() call instead:
// main.js (Electron main process)
// Before (broken):
const { default: open } = require('open');
// After (works):
async function openURL(url) {
const { default: open } = await import('open');
await open(url);
}
Diagnosing a Transitive Dependency Issue
If you never require() the ESM package yourself but still see the error,
a transitive dependency is the culprit — one of your dependencies requires the ESM package.
# Find which package requires the ESM module:
npm ls chalk
# Output example:
my-project@1.0.0
└─┬ some-tool@3.0.0
└── chalk@5.0.0 ← ESM-only chalk pulled in by some-tool
Solutions: (1) pin some-tool to a version that uses chalk@4;
(2) use an npm overrides field to force the sub-dependency version:
// package.json — force chalk@4 everywhere in the tree
{
"overrides": {
"chalk": "^4"
}
}
How to identify if a package is ESM-only
# Check the package.json of the installed version
cat node_modules/chalk/package.json | grep '"type"'
# "type": "module" ← ESM-only
# Also check exports — no "require" key means ESM-only:
cat node_modules/chalk/package.json | grep -A 10 '"exports"'
Also look for the absence of a "main" field pointing to a .js file,
or the presence of only "exports" with "import" conditions and no
"require" condition.
Troubleshooting Checklist
- Read the error message — Node.js prints the exact package name and the
import()fix. - Check
node_modules/<package>/package.jsonfor"type": "module". - Run
node --version— ensure you are on Node.js 14+ (dynamicimport()requires Node 12.17+). - If you never directly require the package, run
npm ls <package>to find the transitive dependency. - Check the package's changelog for the version that went ESM-only.
- Delete
node_modulesandpackage-lock.jsonand runnpm installafter any version changes. - Decide: use
import(), convert to ESM, pin to last CJS version, or useoverrides.
import() or full ESM is the
better long-term choice. Also: if you are using TypeScript, verify your tsconfig module
setting — TypeScript silently converts import to require() when targeting CommonJS.
Frequently Asked Questions
What is ERR_REQUIRE_ESM?
ERR_REQUIRE_ESM is a Node.js error thrown when require() is used to load a package or file that is a pure ES module. ES modules cannot be loaded synchronously — they must be loaded with the asynchronous import() function. The full terminal message starts with: Error [ERR_REQUIRE_ESM]: require() of ES Module ... not supported.
What causes ERR_REQUIRE_ESM?
The most common causes are:
- Upgrading a package (chalk, node-fetch, nanoid, got, execa, etc.) to a version that dropped CommonJS
- Requiring a
.mjsfile - A transitive dependency pulling in an ESM-only package
- TypeScript compiling
importtorequire()withmodule: commonjs - Jest using CommonJS to run tests that import ESM packages
How do I fix ERR_REQUIRE_ESM without rewriting my whole project?
Two options that require minimal changes:
- Pin to the last CJS version:
npm install chalk@4(see table above for other packages) - Wrap with dynamic import:
const { default: chalk } = await import('chalk')— works in any CommonJS file with no other changes needed
How do I fix ERR_REQUIRE_ESM in TypeScript?
TypeScript with "module": "commonjs" in tsconfig compiles import statements to require(), causing the error at runtime. Your options:
- Downgrade to the last CJS version and install matching
@types:npm install node-fetch@2 @types/node-fetch@2.x - Set
"module": "nodenext"and"moduleResolution": "nodenext"in tsconfig (requires TypeScript 4.7+) - Use dynamic
import()in TypeScript:const pkg = await import('package-name')
How do I fix ERR_REQUIRE_ESM in Jest?
Jest uses CommonJS by default. Solutions in order of effort:
- Pin ESM-only packages to their last CJS version (e.g.
npm install chalk@4) - Enable Jest's ESM mode: add
NODE_OPTIONS=--experimental-vm-modulesto your test script and set"type": "module"in package.json - Use Babel with
@babel/preset-envto transform ESM to CJS for tests - Switch to Vitest, which has native ESM support and a Jest-compatible API
Why am I getting ERR_REQUIRE_ESM from a package I never imported directly?
This is a transitive dependency issue. One of your direct dependencies internally requires an ESM-only package. Run npm ls <esm-package-name> to find which package is pulling it in. Then either pin that intermediate package to an older version, or use npm overrides to force the ESM package to a CJS-compatible version across the whole tree.
Can I use require() and import together in the same project?
Yes, but with restrictions. A CJS file can load ESM via import() (dynamic import). An ESM file cannot use require() directly, but can use createRequire(import.meta.url) to load CJS modules. You cannot use static import in a file that Node.js treats as CommonJS (i.e. .js without "type": "module").
Related Errors
ERR_PACKAGE_PATH_NOT_EXPORTED— the package is installed but the subpath is not in itsexportsmapMODULE_NOT_FOUND— the package cannot be found at allERR_IMPORT_ASSERTION_TYPE_MISSING— missing import assertion for JSON modules in ESM