Fix “Cannot use import statement outside a module” in Node.js, Jest and the Browser
On this page
- Why “Cannot use import statement outside a module” happens
- Fix 1: Node.js, add “type”: “module” to package.json
- Fix 2: Use the .mjs extension for one file
- Converting CommonJS code to ES modules
- Fix 3: Jest, three working options
- Option A: Transform with Babel (works everywhere)
- Option B: Native ES module mode
- Option C: Switch to Vitest
- Fix 4: The browser, add type=”module”
- Fix 5: TypeScript and ts-node
- Which fix should you choose?
- Migrating an Express project to ES modules, step by step
- Frequently asked questions about “Cannot use import statement outside a module”
- Oh hi there 👋It’s nice to meet you.
- Sign up to receive awesome content in your inbox.
You add an import line, run your code, and get SyntaxError: Cannot use import statement outside a module. The error means the file was loaded as something that doesn’t understand import: a CommonJS module in Node.js, a test file in Jest’s default mode, or a classic script in the browser. The fix depends on where it happens, so this guide covers all three, and every fix below was tested on current versions.
One thing has changed recently and makes many older answers misleading: on Node.js 22 and newer, a plain .js file with import usually runs anyway, because Node detects the syntax. So if you still see this error today, you are almost certainly in one of the specific situations below.
Why “Cannot use import statement outside a module” happens
JavaScript has two module systems. ES modules (ESM) use import and export, and are the modern standard in browsers and Node.js. CommonJS uses require() and module.exports, and was Node’s original system. A file is loaded as one or the other, and import is only valid in ES modules.
Node.js decides which system a file uses from its extension and the nearest package.json:
| Where you see it | Usual cause | Quickest fix |
|---|---|---|
| Node.js | package.json has "type": "commonjs", the file ends in .cjs, or an older Node.js version |
Set "type": "module" or rename to .mjs |
| Jest | Jest runs tests as CommonJS by default | babel-jest, native ESM mode, or Vitest |
| Browser | Script loaded without type="module" |
<script type="module" src="app.js"> |
| TypeScript output | Compiled to ES modules but run as CommonJS (or the reverse) | Match "module" in tsconfig.json with package.json |
Fix 1: Node.js, add “type”: “module” to package.json
Here is what Node.js 22 prints when package.json declares CommonJS and a .js file uses import:
(node:2886) Warning: Failed to load the ES module: /app/app.js. Make sure to set "type": "module" in the nearest package.json file or use the .mjs extension.
/app/app.js:1
import fs from 'node:fs';
^^^^^^
SyntaxError: Cannot use import statement outside a module
The warning already names both fixes. The most common one is to mark the whole project as ES modules:
{
"name": "my-api",
"type": "module",
"scripts": {
"dev": "node --watch server.js",
"start": "node server.js"
}
}
After this, every .js file in the project is an ES module. Two things change, and both produce new errors if you forget them:
requirestops working. Old code using it fails withReferenceError: require is not defined in ES module scope, you can use import instead. Convert it toimport, as shown below.- Relative imports need the file extension.
import { x } from './util'fails withERR_MODULE_NOT_FOUND; write'./util.js'.
Fix 2: Use the .mjs extension for one file
If you can’t change the whole project, for example in a large CommonJS codebase, rename just the file that uses import to .mjs. Node.js always treats .mjs files as ES modules, whatever package.json says:
node scripts/seed.mjs
The opposite also works: in a project with "type": "module", any file that must stay CommonJS can be renamed to .cjs, where require keeps working. A .cjs file with import in it, on the other hand, always fails with this error.
Converting CommonJS code to ES modules
| CommonJS | ES module |
|---|---|
const express = require('express') |
import express from 'express' |
const { readFile } = require('fs/promises') |
import { readFile } from 'node:fs/promises' |
module.exports = router |
export default router |
module.exports = { add, remove } |
export { add, remove } |
const config = require('./config.json') |
import config from './config.json' with { type: 'json' } |
__dirname |
import.meta.dirname |
__filename |
import.meta.filename |
__dirname doesn’t exist in ES modules; in a test it was simply undefined. Use import.meta.dirname, available in recent Node.js versions. If one old package only works with require, you can still load it from an ES module:
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const legacy = require('some-old-package');
Fix 3: Jest, three working options
Jest is where most developers meet this error today. A test file that imports your ES module code fails out of the box:
FAIL ./math.test.js
● Test suite failed to run
Jest encountered an unexpected token
…
SyntaxError: Cannot use import statement outside a module
Jest runs tests through its own CommonJS module system by default, so import in tests or in the code they load fails. Pick one of these three fixes; all of them passed the same test on current versions.
Option A: Transform with Babel (works everywhere)
npm install -D babel-jest @babel/core @babel/preset-env
// babel.config.cjs
module.exports = {
presets: [['@babel/preset-env', { targets: { node: 'current' } }]],
};
Jest picks up the Babel config automatically and converts import to require before running each test. Keep babel-jest on the same major version as jest. If npm install reports an ERESOLVE conflict, install versions that match your Jest version, for example @babel/core@7 with Jest 29.
Option B: Native ES module mode
Jest can run real ES modules without Babel. You need both of these:
// package.json
{
"type": "module",
"scripts": {
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
}
}
In testing, the flag alone was not enough: without "type": "module", the error remained. Note that in this mode the global jest object is not injected automatically; import it with import { jest } from '@jest/globals' when you need mocks.
Option C: Switch to Vitest
Vitest uses the same describe, test and expect API as Jest and supports ES modules with zero configuration. For new projects, especially ones built with Vite, it is the simplest fix:
npm install -D vitest
import { test, expect } from 'vitest';
import { add } from './math.js';
test('adds', () => {
expect(add(1, 2)).toBe(3);
});
Running npx vitest run passed the test immediately, with no config file.
Fix 4: The browser, add type=”module”
In the browser console the message is the same: Uncaught SyntaxError: Cannot use import statement outside a module. It appears when a script containing import is loaded as a classic script:
<!-- fails: classic script -->
<script src="app.js"></script>
<!-- works: ES module -->
<script type="module" src="app.js"></script>
In a browser test, the first version threw exactly this error and the page script never ran; the second ran correctly. Module scripts also load deferred by default and run in strict mode. Opening the HTML file directly from your disk (file://) blocks module imports for security reasons, so serve the folder with a local server such as npx serve or a Vite dev server. In Vite, React and Next.js projects, the build tool handles this for you; the error there usually comes from a test runner or a Node.js script instead.
Fix 5: TypeScript and ts-node
With TypeScript, the error usually means the compiled output and the runtime disagree. If tsconfig.json has "module": "ESNext" but package.json has no "type": "module", Node.js loads the output as CommonJS and fails. Make them match: for ES modules, use "module": "NodeNext" in tsconfig.json together with "type": "module" in package.json. To run TypeScript files directly in development, tsx handles both module systems without extra setup: npx tsx src/server.ts.
Which fix should you choose?
- New Node.js or MERN backend: add
"type": "module"and write ES modules everywhere. - Large existing CommonJS project: rename individual files to
.mjs, and migrate gradually. - Tests with Jest:
babel-jestfor the least disruption, or move to Vitest if you can. - Browser code without a bundler:
type="module"on the script tag.
For a full Express setup that uses ES modules from the start, see Vite + Express: set up a full-stack MERN project, and for the commands used here, the npm cheat sheet.
Migrating an Express project to ES modules, step by step
If you decide to move a whole Express API to ES modules, doing it in this order avoids a long chain of new errors:
- Check your Node.js version with
node -v, and use a current LTS release so ES module features such asimport.meta.dirnameare available. - Add
"type": "module"topackage.json. - Rename config files that must stay CommonJS, such as
.eslintrc.js,babel.config.jsorjest.config.js, to.cjs. - Replace every
requirewithimport, and everymodule.exportswithexport, file by file. - Add
.jsto relative imports, including folder imports:'./routes'becomes'./routes/index.js'. - Replace
__dirnamewithimport.meta.dirname, for example inexpress.static()paths. - Load environment variables first. With ES modules, imports run before the rest of the file, so
dotenv.config()placed below your imports runs too late. Useimport 'dotenv/config'as the very first line, or Node’s built-innode --env-file=.env server.js. - Run the app and the tests after each group of files, not at the end.
A converted entry file looks like this:
// server.js
import 'dotenv/config';
import express from 'express';
import mongoose from 'mongoose';
import ordersRouter from './routes/orders.js';
const app = express();
app.use(express.json());
app.use('/api/orders', ordersRouter);
await mongoose.connect(process.env.MONGODB_URI); // top-level await works in ES modules
app.listen(process.env.PORT ?? 5000, () => console.log('API running'));
Top-level await is a small bonus of ES modules: you can connect to the database before starting the server without wrapping everything in an async function.
Frequently asked questions about “Cannot use import statement outside a module”
Why does my file work on one computer but not another?
Different Node.js versions. Node.js 22 and newer detect import syntax in .js files without a "type" field and run them as ES modules with a warning; older versions throw the error. Add "type": "module" so every version behaves the same, and set the Node version with an .nvmrc file or the engines field.
Can I use import and require in the same file?
Not directly. A file is either an ES module or CommonJS. In an ES module, create a require function with createRequire for the rare package that needs it.
What does “require is not defined in ES module scope” mean?
It is the opposite problem: the file is an ES module, often after adding "type": "module", but still uses require. Convert those lines to import, or rename the file to .cjs.
Do I need Babel to use import in Node.js?
No. Node.js supports ES modules natively. Babel is only needed for tools that run code as CommonJS, such as Jest in its default mode.
Why do imports fail with ERR_MODULE_NOT_FOUND after switching to ES modules?
ES modules in Node.js need the file extension in relative imports. Change './util' to './util.js'.




