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

**"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](https://mfaysal.com/blog/javascript-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](https://nodejs.org/api/packages.html):

| File | Nearest package.json | Treated as |
| --- | --- | --- |
| `.mjs` | Anything | ES module |
| `.cjs` | Anything | CommonJS |
| `.js` | `"type": "module"` | ES module |
| `.js` | `"type": "commonjs"` | CommonJS |
| `.js` | No `"type"` field | CommonJS, 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](https://docs.npmjs.com/cli/v11/commands/npm-init) 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 version | Result for typeless app.js with import |
| --- | --- |
| 18.20.8 | SyntaxError: Cannot use import statement outside a module |
| 20.20.2 | Runs, with a MODULE\_TYPELESS\_PACKAGE\_JSON warning |
| 22.23.4 | Runs, with the same warning |
| 24.22.1 | Runs, with the same warning |
| 26.11.1 | Runs, 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](https://nodejs.org/en/about/previous-releases)). 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](https://www.typescriptlang.org/tsconfig/#module) 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](https://jestjs.io/docs/ecmascript-modules) 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](https://jestjs.io/docs/configuration). 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) 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](https://nodejs.org/api/esm.html) 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](https://mfaysal.com/blog/javascript-async-await-tutorial) covers it from scratch.

## Quick fix checklist

| Where you see it | Most likely cause | Fix |
| --- | --- | --- |
| `node app.js`, new project | `"type": "commonjs"` from `npm init -y` | Change to `"type": "module"` |
| Node, one file only | Project must stay CommonJS | Rename that file to `.mjs` |
| A `.cjs` file | `.cjs` is always CommonJS | Use `require()` or rename to `.mjs` |
| A script in a subfolder | Nested `package.json` | Remove it or set its type to module |
| Node 18 or older | No syntax detection | Upgrade Node and set the type explicitly |
| `dist/*.js` from `tsc` | `"module": "ESNext"` in a CommonJS package | Use `NodeNext` + module type, or `CommonJS` |
| Jest, test file | Jest loads tests as CommonJS | Babel preset or `--experimental-vm-modules` |
| Jest, file in node\_modules | ESM-only dependency not transformed | `transformIgnorePatterns` |
| Browser console | Classic `<script>` | `type="module"`, served over http |

Working with keys and IDs while you debug? The [free developer tools on mfaysal.com](https://mfaysal.com/free-tools/) 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.*

---

*Originally published on [mfaysal.com](https://mfaysal.com/blog/cannot-use-import-statement-outside-a-module).*

I'm learning cybersecurity on TryHackMe — follow my progress at [tryhackme.com/p/mfaysal.dev](https://tryhackme.com/p/mfaysal.dev).
