ERR_REQUIRE_ESM after an agent added "type": "module"
Any AI coding tool Last checked 2026-10-31
ERR_REQUIRE_ESM means a require() call hit a package or local file that Node now treats as an ES module — almost always because an agent added "type": "module" to package.json (or renamed a file to .mjs) in the same edit that was supposed to fix something unrelated, and every require() in the project broke at once.
What you saw
Error [ERR_REQUIRE_ESM]: require() of ES Module /app/index.js from /app/server.js not supported.
The app ran fine a few edits ago. After the agent's last change — often framed as "fixed the import error" or "added top-level await support" — every require() call in the project throws ERR_REQUIRE_ESM, not just the one file the agent touched.
The agent added "type": "module" to package.json to make one new import statement work, without checking whether anything else in the project still used require().
A dependency update (node-fetch 3, chalk 5, nanoid 4, execa 6+) shipped ESM-only, the agent chased the resulting error, and its fix was to flip the whole project to ESM rather than scope the change to the one file that imports that package.
Jest, ts-node, or a build script that was working now fails with the same error, because they still assume CommonJS and the agent's package.json change silently changed what every .js file in the project means to Node.
Renaming a file's extension to .mjs (or .cjs on the other side) made that one file work and broke every other file that still require()'d it by its old .js path.
What it actually means
Node decides whether a .js file is CommonJS or an ES module from the nearest parent package.json's "type" field — "module" means ESM, missing or "commonjs" means CommonJS. That field is project-wide by default; it is not a per-file or per-import setting unless you also use .mjs/.cjs extensions.
require() is a CommonJS-only function. It cannot load a file or package that Node has decided is an ES module, full stop — there is no flag that makes it work. That is what ERR_REQUIRE_ESM is reporting: not a missing file, a module-system mismatch.
Agents tend to treat "add type: module" as a generic fix for any ESM-related import error, because it is the first result for the error text and it does make the one failing import go away. It does not check whether the other 40 files in the project still call require().
This is a platform-wide flip, not a local patch: every .js file under that package.json instantly becomes ESM the moment the field is added, including test runners, build scripts and config files that were never part of the original problem.
The reverse direction exists too and is just as agent-triggered: renaming a single file to .mjs to "force" ESM for one import, which fixes that file and breaks every other file in the project that still requires it under its old .js extension.
The fix
First, find out what actually changed. Diff package.json and any renamed files against the last working commit — the agent's summary rarely mentions a one-line "type" field add.
git diff HEAD~1 -- package.json
git diff --name-status HEAD~1 | grep -E '\.(m|c)?js$'
If the project is genuinely still CommonJS everywhere except the one import that triggered this, the fastest correct fix is to revert the project-wide flip and load that one ESM-only package with a dynamic import instead of require().
// wrong — require() can never load ESM
const fetch = require('node-fetch');
// right — dynamic import works from CommonJS
const { default: fetch } = await import('node-fetch');
If dynamic import isn't convenient at that call site (e.g. it's not in an async function), isolate the ESM-only dependency behind a small wrapper module instead of converting the whole project.
// esm-bridge.cjs
module.exports.getFetch = () => import('node-fetch').then(m => m.default);
If the project genuinely should move to ESM (multiple dependencies are ESM-only, or you want top-level await everywhere), do it as its own commit, then fix every require() call it breaks in the same commit — not as a side effect of an unrelated fix.
// after "type": "module", every require() must become import
// wrong
const { helper } = require('./utils');
// right
import { helper } from './utils.js'; // extension is mandatory in ESM
Check which runner is actually failing — Jest and older ts-node setups assume CommonJS by default and need their own config change if the project is ESM now. Don't assume fixing the app also fixes the test suite.
node --experimental-vm-modules node_modules/.bin/jest # Jest + ESM
node --loader ts-node/esm src/index.ts # ts-node + ESM
If a single file was renamed to .mjs instead of the whole project being converted, either rename it back and fix the one import that needed ESM with dynamic import, or update every file that requires it to use import with the .mjs extension.
grep -rn "require(.*old-file-name" --include='*.js' .
How to stop it happening again
Tell the agent explicitly, in your rules file or system prompt, that package.json's "type" field is project-wide and must never be changed to fix a single import — the fix for one ESM-only dependency is a dynamic import or a wrapper module, not a module-system flip.
After any agent change that touches package.json, run the full test suite and the build script, not just the file the agent was working on — a module-system flip breaks files it never touched.
Pin ESM-only dependencies' versions deliberately rather than letting an agent "just update it" — node-fetch, chalk, nanoid and execa have all shipped ESM-only majors that silently change what require() can do with them.
If the project is intentionally mixed (some ESM, some CommonJS), use explicit .mjs/.cjs extensions on the boundary files instead of relying on a single project-wide "type" field, so an agent's local edit cannot flip the whole project by accident.
Related questions
Can I just add "type": "module" and fix every require() call after?
Yes, that is a valid path — but it means converting every require()/module.exports in the project to import/export, including test runners and build scripts, in the same change. Treat it as a migration, not a one-line fix, and expect to touch files the original error never mentioned.
Why did this only break after a dependency update, not an agent edit?
The agent edit is usually the response to the break, not the cause — node-fetch 3, chalk 5 and several other popular packages dropped CommonJS support in a major version. The agent then "fixed" the resulting import error by flipping the whole project to ESM instead of scoping a dynamic import to that one dependency.
Is dynamic import() always the safer fix than converting the whole project?
For one ESM-only dependency in an otherwise-CommonJS project, yes — it's a one-line, localized change. Convert the whole project only when you have multiple ESM-only dependencies or actively want ESM features like top-level await everywhere; otherwise you're trading one error for a larger, riskier migration.
Does this affect TypeScript projects the same way?
The underlying rule is identical — Node still decides CommonJS vs ESM from package.json's "type" field at runtime, after ts-node or tsc compiles your TypeScript down to JS. ts-node in particular needs its own ESM loader flag once the project flips, so a TypeScript project can look fine at compile time and still throw ERR_REQUIRE_ESM at run time.