Coding tips

SyntaxError: Cannot Use Import Statement Outside a Module: 9 Causes and Fixes (Node.js, TypeScript, Jest and Browser, Tested Examples)

This error means a file using import is being run as CommonJS or as a classic browser script. Here are the 9 real causes, from npm's new "type": "commonjs" default to Jest and TypeScript, each reproduced with real output and the exact fix.

By 15 min read 1 view
Cover image for SyntaxError: Cannot Use Import Statement Outside a Module: 9 Causes and Fixes (Node.js, TypeScript, Jest and Browser, Tested Examples)

"SyntaxError: Cannot use import statement outside a module" means the file containing import is being loaded as a CommonJS script (in Node.js or Jest) or as a classic <script> (in the browser), and import is only allowed inside ES modules. The quickest fix in Node.js is to add "type": "module" to your package.json or rename the file to .mjs. In the browser, use <script type="module">. In Jest, either transform the file with Babel or run Jest in native ESM mode.

The catch in 2026 is that the most common trigger has changed. Modern Node.js versions can detect ES module syntax on their own, so a plain app.js with import usually just works now. But npm init -y in recent npm versions (I tested 11.21.1) writes "type": "commonjs" into every new package.json, and that one line switches the detection off. That's why so many people hit this error on a brand-new project.

I reproduced every cause below on the box I use for testing: Node.js 24.22.1 (plus 18.20.8, 20.20.2, 22.23.4 and 26.11.1 for comparison), npm 11.21.1, TypeScript 7.0.2, Jest 30.5.2 and Chrome 156. Every output block is copied from those runs.

In this guide:

  • What the error looks like
  • Why it happens: CommonJS vs ES modules
  • Cause 1: package.json says "type": "commonjs" (npm init -y adds it)
  • Cause 2: you need CommonJS for the project but ESM for one file
  • Cause 3: the file ends in .cjs
  • Cause 4: a nested package.json in a subfolder
  • Cause 5: an old Node.js version
  • Cause 6: TypeScript outputs import but the package is CommonJS
  • Cause 7: Jest runs your test file as CommonJS
  • Cause 8: an ESM-only package in node_modules (Jest)
  • Cause 9: a browser script tag without type="module"
  • Errors you'll see right after switching to ESM
  • Keeping CommonJS but using an ESM-only package
  • Quick fix checklist
  • Frequently asked questions

What the error looks like

Here's a tiny script that reads its own package.json with an import and top-level await:

import { readFile } from 'node:fs/promises';
const pkg = JSON.parse(await readFile('./package.json', 'utf8'));
console.log('Loaded', pkg.name);

Inside a project created with npm init -y, running node app.js on Node.js 24.22.1 gives this (stack trace trimmed after the first frame):

(node:81576) Warning: Failed to load the ES module: /workspace/seo-posts-nov01/test/node/c2/app.js. Make sure to set "type": "module" in the nearest package.json file or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/workspace/seo-posts-nov01/test/node/c2/app.js:1
import { readFile } from 'node:fs/promises';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at wrapSafe (node:internal/modules/cjs/loader:1861:18)

Read the warning line above the error. Node tells you exactly what it wants: set "type": "module" in the nearest package.json, or use the .mjs extension. The cjs/loader in the stack trace confirms the file went through the CommonJS loader. If your console shows a different message, such as TypeError: Cannot read properties of undefined, that's a runtime bug rather than a module problem, and my guide to fixing Cannot read properties of undefined covers it.

Why it happens: CommonJS vs ES modules

Node.js has two module systems. CommonJS is the original one, with require() and module.exports. ES modules (ESM) are the JavaScript standard, with import and export. A file is parsed as one or the other, never both, and import statements are a syntax error in CommonJS.

Node picks the module system for each file using a short list of rules, documented in the official Node.js packages documentation:

FileNearest package.jsonTreated as
.mjsAnythingES module
.cjsAnythingCommonJS
.js"type": "module"ES module
.js"type": "commonjs"CommonJS
.jsNo "type" fieldCommonJS, unless ES module syntax is detected (Node 20.19+ and 22.7+)

The last row matters. Syntax detection is enabled by default from Node.js v22.7.0 and v20.19.0, so a typeless .js file with import gets a second chance. An explicit "type": "commonjs", or a .cjs extension, gets no second chance.

Cause 1: package.json says "type": "commonjs" (npm init -y adds it)

This is the cause I see most often now. Here's the package.json that npm init -y generated with npm 11.21.1:

{
  "name": "c2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs"
}

The last line tells Node to treat every .js file in the project as CommonJS, so the import in app.js fails with the error above. I got the identical error on Node.js 26.11.1.

Fix: change it to "module". You can edit the file by hand or run npm pkg set type=module:

"type": "module"

Same file, same folder, after the change:

$ node app.js
Loaded c2

If you're starting a new project and want to write import, set this once and forget about it. The npm init documentation also lists an init-type config if you'd rather have every new project start as a module.

Cause 2: you need CommonJS for the project but ESM for one file

Sometimes you can't flip the whole project. Maybe older files rely on require() and __dirname. In that case, rename just the file that uses import to .mjs. The .mjs extension always means ES module, whatever package.json says.

$ mv app.js app.mjs
$ node app.mjs
Loaded c2

That ran in the same folder that still has "type": "commonjs". Remember to update any import paths that point at the renamed file, because ES modules need the full file name including the extension.

Cause 3: the file ends in .cjs

The reverse also happens. A config file called config.cjs is always CommonJS, so an import inside it fails even when package.json says "type": "module":

import os from 'node:os';
module.exports = { cpus: os.cpus().length };
/workspace/seo-posts-nov01/test/node/c3/config.cjs:1
import os from 'node:os';
^^^^^^

SyntaxError: Cannot use import statement outside a module

Fix: pick one style per file. Either keep .cjs and use const os = require('node:os');, or rename it to .mjs (or .js in a module project) and use export default instead of module.exports.

Cause 4: a nested package.json in a subfolder

Node uses the nearest package.json, walking up from the file. A stray one in a subfolder wins over the one at the project root. In my test, the root said "type": "module", but scripts/package.json contained { "type": "commonjs" }:

$ node scripts/seed.js
/workspace/seo-posts-nov01/test/node/c4/scripts/seed.js:1
import { randomUUID } from 'node:crypto';
^^^^^^

SyntaxError: Cannot use import statement outside a module

Look closely at the warning Node prints and you'll see it says "the nearest package.json". After I deleted scripts/package.json, the file picked up the root setting:

$ node scripts/seed.js
seed id 36

This one is easy to miss in monorepos, generated folders and copied example code. Run find . -name package.json -not -path "*/node_modules/*" to list them all.

Cause 5: an old Node.js version

Before syntax detection existed, Node treated every typeless .js file as CommonJS. I ran the same app.js, in a folder whose package.json has no "type" field, on five versions:

Node.js versionResult for typeless app.js with import
18.20.8SyntaxError: Cannot use import statement outside a module
20.20.2Runs, with a MODULE_TYPELESS_PACKAGE_JSON warning
22.23.4Runs, with the same warning
24.22.1Runs, with the same warning
26.11.1Runs, with the same warning

Here's the Node 18 output:

(node:81508) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/workspace/seo-posts-nov01/test/node/c1/app.js:1
import { readFile } from 'node:fs/promises';
^^^^^^

SyntaxError: Cannot use import statement outside a module

And here's the warning newer versions print instead (Node 24.22.1):

(node:81540) [MODULE_TYPELESS_PACKAGE_JSON] Warning: Module type of file:///workspace/seo-posts-nov01/test/node/c1/app.js is not specified and it doesn't parse as CommonJS.
Reparsing as ES module because module syntax was detected. This incurs a performance overhead.
To eliminate this warning, add "type": "module" to /workspace/seo-posts-nov01/test/node/c1/package.json.
(Use `node --trace-warnings ...` to show where the warning was created)
Loaded c1

Fix: upgrade to a supported release line (check node -v; Node 18 and 20 are end-of-life on the Node.js releases page). Then add "type": "module" anyway. The warning says detection "incurs a performance overhead", and being explicit means your code behaves the same on every machine and CI runner.

Cause 6: TypeScript outputs import but the package is CommonJS

TypeScript adds a twist: the code you write is .ts, but Node runs the .js files that tsc produces. If tsconfig.json says "module": "ESNext", the compiler keeps your import statements. Put that output in a package with "type": "commonjs" and it breaks:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true
  }
}
$ npx tsc && node dist/index.js
/workspace/seo-posts-nov01/test/node/ts/dist/index.js:1
import { greet } from './greet.js';
^^^^^^

SyntaxError: Cannot use import statement outside a module

The compile step succeeded, which is why this one is confusing. The problem only shows up at runtime. You have two consistent setups to choose from.

Fix A, ESM all the way: set "type": "module" in package.json and use "module": "NodeNext" with "moduleResolution": "NodeNext", which makes TypeScript follow Node's own rules:

$ npx tsc -v
Version 7.0.2
$ npx tsc && node dist/index.js
Hello, Mahir

Fix B, CommonJS output: keep "type": "commonjs" and set "module": "CommonJS". TypeScript then rewrites your imports as require() calls. This is the start of the compiled dist/index.js:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const greet_js_1 = require("./greet.js");
console.log((0, greet_js_1.greet)('Mahir'));

The TypeScript module option reference explains each value. For new Node projects, NodeNext is the safe choice because it matches what Node actually does with your files.

Cause 7: Jest runs your test file as CommonJS

Jest is the other big source of this error. Out of the box, Jest loads test files as CommonJS, so this test fails before a single assertion runs:

import { nanoid } from 'nanoid';
test('makes a 21-char id', () => {
  expect(nanoid()).toHaveLength(21);
});
FAIL ./id.test.js
  ● Test suite failed to run

    Jest encountered an unexpected token
    ...
    Details:

    /workspace/seo-posts-nov01/test/node/jest1/id.test.js:1
    import { nanoid } from 'nanoid';
    ^^^^^^

    SyntaxError: Cannot use import statement outside a module

I've cut the middle of Jest's long help message (marked with ...), but it's worth reading in full, because it lists the fixes. There are two good routes.

Route 1, native ESM: set "type": "module" and run Jest with Node's VM modules flag. With Jest 30.5.2 on Node 24.22.1, that passed:

$ NODE_OPTIONS=--experimental-vm-modules npx jest
(node:82155) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total

Setting "type": "module" without the flag isn't enough. Jest 30.5.2 then failed with "Must use import to load ES Module" for the test file. The Jest ECMAScript modules guide covers this mode and its limits.

Route 2, Babel: stay on CommonJS and let babel-jest convert import to require(). Add a babel.config.js:

module.exports = {
  presets: [['@babel/preset-env', { targets: { node: 'current' } }]],
};

A gotcha from my run: npm i -D @babel/preset-env now installs Babel 8, which refused to install next to Jest 30's Babel 7 with an ERESOLVE peer dependency error. Pinning @babel/preset-env@7 @babel/core@7 worked. That fixed the test file, which leads straight to cause 8.

Cause 8: an ESM-only package in node_modules (Jest)

With Babel in place, the test file compiled, but Jest now choked on the dependency itself:

FAIL ./id.test.js
  ● Test suite failed to run

    Must use import to load ES Module: /workspace/seo-posts-nov01/test/node/jest1/node_modules/nanoid/index.js

Notice the path: the failing file is now inside node_modules. Jest doesn't transform anything in node_modules by default, and packages like nanoid 5 ship only ES modules.

Fix: tell Jest to transform that package with transformIgnorePatterns in jest.config.js:

module.exports = {
  transformIgnorePatterns: ['/node_modules/(?!nanoid)'],
};
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total

Add more package names inside the brackets with a pipe, for example (?!nanoid|chalk). The pattern is described in the Jest configuration docs. I used --no-cache while testing, because Jest caches transformed files and an old cached result can hide a config change.

Cause 9: a browser script tag without type="module"

Browsers have the same rule. A regular <script src="app.js"> is a classic script, and import inside it fails. I loaded this page in Chrome 156 through a local web server:

<script src="app.js"></script>
Uncaught SyntaxError: Cannot use import statement outside a module

Fix: add type="module":

<script type="module" src="app.js"></script>
2 + 3 = 5

One more trap: modules don't load from file://. When I double-clicked the fixed page instead of using a server, Chrome blocked it:

Access to script at 'file:///workspace/seo-posts-nov01/test/node/browser/app.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: chrome, chrome-experimental-site-token-provider, chrome-extension, chrome-untrusted, data, http, https, isolated-app.

Run any local server, for example npx serve or python3 -m http.server, and open the http:// address. MDN's JavaScript modules guide explains the rest of the browser rules.

Errors you'll see right after switching to ESM

Fixing the import error often reveals the next one, because some CommonJS features don't exist in ES modules. After switching to "type": "module", an old file that mixed styles gave me this:

ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension and '/workspace/seo-posts-nov01/test/node/e1/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.

And for __dirname:

ReferenceError: __dirname is not defined in ES module scope

Both have clean replacements. import.meta.dirname and import.meta.filename replace __dirname and __filename, and createRequire gives you a real require if you still need one:

import path from 'node:path';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const fs = require('fs');
console.log(path.join(import.meta.dirname, 'data.json'), typeof fs);
console.log(import.meta.filename);
/workspace/seo-posts-nov01/test/node/e1/data.json object
/workspace/seo-posts-nov01/test/node/e1/fixed.js

The Node.js ES modules documentation lists import.meta.dirname as added in v20.11.0 and stable since v22.16.0 and v24.0.0.

Keeping CommonJS but using an ESM-only package

Maybe you don't want ESM at all, you just need one modern package. You don't have to convert the project. I tested two approaches in a plain CommonJS project with nanoid 5.

require() of an ES module works on modern Node. The Node.js 22.12.0 release notes say "require(esm) is now enabled by default":

const { nanoid } = require('nanoid');
console.log('id length:', nanoid().length);
-- v24.22.1
id length: 21
-- v18.20.8
Error [ERR_REQUIRE_ESM]: require() of ES Module /workspace/seo-posts-nov01/test/node/c5/node_modules/nanoid/index.js from /workspace/seo-posts-nov01/test/node/c5/req.js not supported.
Instead change the require of index.js in /workspace/seo-posts-nov01/test/node/c5/req.js to a dynamic import() which is available in all CommonJS modules.

Dynamic import() works in every version I tried, including 18, because it's allowed in CommonJS files. It returns a promise, so use it inside an async function:

async function main() {
  const { nanoid } = await import('nanoid');
  console.log('id length:', nanoid().length);
}
main();
id length: 21

If await is new to you, my async/await tutorial covers it from scratch.

Quick fix checklist

Where you see itMost likely causeFix
node app.js, new project"type": "commonjs" from npm init -yChange to "type": "module"
Node, one file onlyProject must stay CommonJSRename that file to .mjs
A .cjs file.cjs is always CommonJSUse require() or rename to .mjs
A script in a subfolderNested package.jsonRemove it or set its type to module
Node 18 or olderNo syntax detectionUpgrade Node and set the type explicitly
dist/*.js from tsc"module": "ESNext" in a CommonJS packageUse NodeNext + module type, or CommonJS
Jest, test fileJest loads tests as CommonJSBabel preset or --experimental-vm-modules
Jest, file in node_modulesESM-only dependency not transformedtransformIgnorePatterns
Browser consoleClassic <script>type="module", served over http

Working with keys and IDs while you debug? The free developer tools on mfaysal.com include a UUID generator, a Base64 encoder and decoder and a URL encoder that all run in your browser.

Frequently asked questions

What does "Cannot use import statement outside a module" mean?

It means JavaScript found an import statement in a file that is being run as CommonJS (in Node.js or Jest) or as a classic browser script. Import is only valid inside ES modules, so the engine stops with a SyntaxError before running any code.

How do I fix it in Node.js quickly?

Add "type": "module" to the nearest package.json, or rename the file to .mjs. On new projects, check for "type": "commonjs", because npm init -y added that line by default in npm 11.21.1, the version I tested.

Why does my file work without a type field but fail after npm init?

Node.js 20.19+ and 22.7+ detect import syntax in .js files when package.json has no type field. An explicit "type": "commonjs" turns that detection off, so the same file now fails.

Should I use "type": "module" or .mjs?

Use "type": "module" for new projects where every file uses import. Use .mjs when the rest of the project must stay CommonJS and only a few files need ES module syntax.

How do I fix this error in Jest?

Either run Jest in native ESM mode with "type": "module" and NODE_OPTIONS=--experimental-vm-modules, or add a Babel preset so babel-jest converts imports to require. If the error points into node_modules, add that package to transformIgnorePatterns.

Why does TypeScript compile fine but Node still throws the error?

tsc only checks and emits code. With "module": "ESNext" it keeps import statements, and Node then runs them as CommonJS if package.json says commonjs. Use "module": "NodeNext" with "type": "module", or switch to "module": "CommonJS".

What is the difference between this error and "Unexpected token 'export'"?

They have the same cause. A CommonJS file that contains export instead of import fails with SyntaxError: Unexpected token 'export'. I got exactly that on Node 24.22.1 when a CommonJS file required a .js file containing export, and the same fixes apply.

Can I use require and import in the same file?

Not directly. In an ES module, require is not defined, but you can create one with createRequire(import.meta.url) from node:module. In a CommonJS file, use dynamic import() to load an ES module.

Last checked: 1 November 2026. Figures, settings and guidance can change, so confirm anything important with the sources linked above.

Share X LinkedIn WhatsApp Facebook
Was this helpful?

0%