Node.js Error

ERR_REQUIRE_ESM

Complete reference for Error [ERR_REQUIRE_ESM]: require() of ES Module ... not supported — what it means, every cause, and all fixes including TypeScript, Jest, and transitive dependencies.

Quick Answer: Node.js throws 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;
}
Named vs default exports: ESM packages often use a default export. Use { 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);
Local imports need file extensions in ESM: Unlike CommonJS, ESM requires explicit file extensions: 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.

PackageLast CJS versionFirst ESM-only versionInstall command
chalk4.1.2v5npm install chalk@4
node-fetch2.6.7v3npm install node-fetch@2
nanoid3.xv4npm install nanoid@3
got11.8.3v12npm install got@11
execa5.xv6npm install execa@5
p-map4.xv5npm install p-map@4
ora5.xv6npm install ora@5
open8.xv9npm install open@8
figures3.xv5npm install figures@3
find-up5.xv6npm install find-up@5
p-limit3.xv5npm install p-limit@3
slash3.xv5npm install slash@3
gulp-imagemin7.1.0v8npm 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:

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

  1. Read the error message — Node.js prints the exact package name and the import() fix.
  2. Check node_modules/<package>/package.json for "type": "module".
  3. Run node --version — ensure you are on Node.js 14+ (dynamic import() requires Node 12.17+).
  4. If you never directly require the package, run npm ls <package> to find the transitive dependency.
  5. Check the package's changelog for the version that went ESM-only.
  6. Delete node_modules and package-lock.json and run npm install after any version changes.
  7. Decide: use import(), convert to ESM, pin to last CJS version, or use overrides.
Common mistake: Pinning to an old CJS version is quick but means missing security patches and new features. Migrating to 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 .mjs file
  • A transitive dependency pulling in an ESM-only package
  • TypeScript compiling import to require() with module: 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:

  1. Pin to the last CJS version: npm install chalk@4 (see table above for other packages)
  2. 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:

  1. Pin ESM-only packages to their last CJS version (e.g. npm install chalk@4)
  2. Enable Jest's ESM mode: add NODE_OPTIONS=--experimental-vm-modules to your test script and set "type": "module" in package.json
  3. Use Babel with @babel/preset-env to transform ESM to CJS for tests
  4. 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