Fix ERR_REQUIRE_ESM: require() of ES Module not supported in Node.js

intermediate๐Ÿ’š Node.js2026-03-22| Node.js 12+, npm/pnpm/yarn projects, CommonJS projects importing ESM-only packages (e.g. node-fetch v3, chalk v5, nanoid v4+)

Error Message

Error [ERR_REQUIRE_ESM]: require() of ES Module /path/to/module.mjs not supported
#nodejs#esm#require#commonjs#module

The Scenario

It's 2 AM. Your deploy just failed, or your local build exploded after bumping a dependency. The stack trace looks like this:

Error [ERR_REQUIRE_ESM]: require() of ES Module /path/to/node_modules/node-fetch/src/index.js not supported.
/path/to/node_modules/node-fetch/src/index.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package as ES modules.
Instead either rename /path/to/node_modules/node-fetch/src/index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /path/to/node_modules/node-fetch/src/index.js.
    at Object. (/your/project/src/api.js:3:18)

Your code didn't change โ€” only the package version did. Welcome to the CommonJS vs ESM compatibility wall.

What's Actually Happening

Node.js supports two module systems:

  • CommonJS (CJS): uses require() / module.exports โ€” the classic Node.js way
  • ESM (ES Modules): uses import / export โ€” the modern standard

The problem: CJS cannot require() an ESM module. Full stop. Starting around 2021, popular packages like node-fetch v3, chalk v5, nanoid v4, got v12, and execa v6 dropped CJS support entirely and went ESM-only. The moment you upgrade one of these, any project still using require() hits a wall.

Identify the Root Cause First

Before picking a fix, confirm what module system your project uses:

cat package.json | grep '"type"'
  • No type field or "type": "commonjs" โ†’ your project is CJS
  • "type": "module" โ†’ your project is ESM

Then check the offending package:

cat node_modules/node-fetch/package.json | grep '"type"'

If it says "type": "module", that package is ESM-only โ€” you can't require() it. Now you know the enemy. Pick your fix below.

Quick Fix: Pin to the Last CJS Version

Need it working in the next 5 minutes? Pin the package to its last CommonJS-compatible version. No code changes required.

# node-fetch: last CJS version is v2
npm install node-fetch@2

# chalk: last CJS version is v4
npm install chalk@4

# nanoid: last CJS version is v3
npm install nanoid@3

# got: last CJS version is v11
npm install got@11

# execa: last CJS version is v5
npm install execa@5

Verify the fix:

node -e "const fetch = require('node-fetch'); console.log(typeof fetch);"

It should print function. You're unblocked. Ship it.

Permanent Fix Option 1: Use Dynamic import()

Want the latest package version without converting your whole project? Dynamic import() is your bridge. CJS files can call import() โ€” they just can't use the static import keyword at the top of the file.

// Before (broken)
const fetch = require('node-fetch');

// After (works in CJS files)
async function fetchData(url) {
  const { default: fetch } = await import('node-fetch');
  const res = await fetch(url);
  return res.json();
}

Notice the { default: fetch } destructuring. When you access an ESM default export via dynamic import from CJS, it lands on the default property โ€” not directly as the value.

Quick test to confirm it works:

node -e "
async function test() {
  const { default: fetch } = await import('node-fetch');
  const res = await fetch('https://httpbin.org/get');
  console.log(res.status);
}
test();
"

Permanent Fix Option 2: Convert Your Project to ESM

For a clean long-term solution, migrate the whole project to ESM. More work upfront โ€” but it eliminates the compatibility problem entirely.

Step 1: Update package.json

{
  "type": "module"
}

Step 2: Replace require/module.exports with import/export

// Before (CJS)
const express = require('express');
const { readFileSync } = require('fs');
module.exports = { myFunction };

// After (ESM)
import express from 'express';
import { readFileSync } from 'fs';
export { myFunction };

Step 3: Fix __dirname and __filename (they don't exist in ESM)

// Add this at the top of files that need __dirname
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

Step 4: Fix dynamic requires

// Before
const config = require(`./configs/${env}.js`);

// After
const { default: config } = await import(`./configs/${env}.js`);

Run the project and see what breaks:

node src/index.js

Expect a few more edge cases โ€” JSON imports, require.resolve, and the occasional older package with no ESM support. Fix them as they surface.

Permanent Fix Option 3: Use a Bundler or Transpiler

Manual migration too risky? Let a bundler handle the CJS/ESM conversion for you.

# esbuild โ€” the fastest option
npm install -D esbuild
npx esbuild src/index.js --bundle --platform=node --outfile=dist/index.cjs

# tsx โ€” great for TypeScript projects
npm install -D tsx
npx tsx src/index.ts

TypeScript users can stay on CJS output while writing ESM-style imports:

// tsconfig.json
{
  "compilerOptions": {
    "module": "CommonJS",
    "esModuleInterop": true
  }
}

Special Case: Running in Jest

Jest defaults to CJS mode, so ESM packages will break your tests too. Two options:

# Option 1: enable ESM support in Jest
NODE_OPTIONS='--experimental-vm-modules' npx jest

# Option 2: tell Jest to transform the ESM package
module.exports = {
  transformIgnorePatterns: [
    'node_modules/(?!(node-fetch|chalk|nanoid)/)',
  ],
};

Option 1 is simpler to set up. Option 2 gives you finer control when multiple ESM-only packages are involved.

Verification

Run these three checks after any fix:

# 1. Run your entry point directly
node src/index.js

# 2. Run your test suite
npm test

# 3. Spot-check the specific import
node -e "import('node-fetch').then(m => console.log('OK:', typeof m.default))"

No ERR_REQUIRE_ESM anywhere in the output? Done.

Which Fix to Choose

  • Need it fixed in 5 minutes: pin to last CJS version
  • Want latest package, minimal refactor: use dynamic import()
  • Greenfield or willing to refactor: convert project to ESM
  • TypeScript project: set "module": "CommonJS" in tsconfig + esModuleInterop: true

Related Error Notes