Skip to content
Nilay Kabariya

Entry No.001·Fixes·Updated ·6 min read

Fix: “Cannot use import statement outside a module”

Why Node, Jest, TypeScript output or your browser refuses import (or throws Unexpected token 'export'), and the fix for each. Tested on Node 20 to 26.

by Nilay#node#esm#javascriptFIXED

The error, verbatim

Jump to the fix ↓
SyntaxError: Cannot use import statement outside a module

Uncaught SyntaxError: Cannot use import statement outside a module

SyntaxError: Unexpected token 'export'
✓ Reproduced on Ubuntu on GitHub Actions; Windows 11 Pro

Tested on

Node
20.20.2, 22.23.4, 24.22.1, 26.13.0 (Linux); 24.14.0 (Windows)
npm
10.8.2, 10.9.9, 11.9.0, 11.19.1, 11.20.0, 12.2.1
Jest / TypeScript
Jest 30 (30.5.2 with nanoid 5.1.16), TypeScript 6.0.3
Browser
Chrome 152; Chromium (built-in browser)
OS
Ubuntu on GitHub Actions; Windows 11 Pro

✓ Last re-run on GitHub Actions · see the run · all repros

Contents
  1. The fix01
  2. When does it still happen?02
  3. In Jest03
  4. In compiled TypeScript04
  5. node -e works now05
  6. SyntaxError: Unexpected token ‘export’06
  7. Why it happens07
  8. What didn’t work08
  9. How this was tested09

Seeing SyntaxError: Unexpected token 'export' instead? Same cause, see the section below.

The fix

The file uses import, but whatever runs it treats it as a classic script or CommonJS. Tell it the file is a module.

In the browser

Add type="module" to the script tag:

<script type="module" src="app.js"></script>

Module scripts don’t load from file:// pages. Open the page through a local server (for example npx vite or npx serve) instead of double-clicking the HTML file.

In Node

Use one of these:

// package.json
{ "type": "module" }

or rename the file to .mjs.

When does it still happen?

Current Node detects import syntax on its own, so the error is rarer than older answers suggest. The same file on four Node versions gave the same result on all of them (20.20.2, 22.23.4, 24.22.1, 26.13.0):

File package.json Result
app.js no "type" field Runs, with a MODULE_TYPELESS_PACKAGE_JSON warning
app.js "type": "commonjs" SyntaxError
app.cjs anything, even "type": "module" SyntaxError
app.js "type": "module" Runs
app.mjs anything, even "type": "commonjs" Runs

So on current Node you’ll usually see it in three places: a project that says "type": "commonjs", a test runner, or compiled TypeScript.

In Jest

A test file that uses import failed on every Node version with Jest 30 and no config:

FAIL ./sum.test.js
    SyntaxError: Cannot use import statement outside a module

Jest runs tests as CommonJS unless you tell it otherwise. Adding "type": "module" alone didn’t help: the test still failed.

Option 1: let Babel convert the imports. Install babel-jest, @babel/core and @babel/preset-env, then add babel.config.json:

{ "presets": [["@babel/preset-env", { "targets": { "node": "current" } }]] }

Option 2: run Jest as real ES modules. Set "type": "module" in package.json and start Jest with the VM modules flag:

NODE_OPTIONS=--experimental-vm-modules npx jest

In compiled TypeScript

tsc copies your import lines into the output as they are when module is esnext. If package.json says CommonJS, running the output fails:

$ node dist/index.js
SyntaxError: Cannot use import statement outside a module

Match module to how Node will run the file. With "module": "nodenext", TypeScript reads your package.json and emits require() for a CommonJS package:

{ "compilerOptions": { "module": "nodenext", "moduleResolution": "nodenext", "rootDir": "src", "outDir": "dist" } }

Or keep esnext and set "type": "module", so Node loads the output as an ES module.

node -e works now

A one-liner with import ran fine with node -e on all four versions, without --input-type=module. If an old answer tells you to add that flag for -e, you no longer need it.

The warning in the first row goes away when you add "type": "module". That also skips a second parse, which Node says costs performance.

SyntaxError: Unexpected token ‘export’

This is the same problem from the other side: code using export was loaded as CommonJS by Node or Jest, or as a classic script by the browser.

In Node

On Windows 11 Pro, Node 24.14.0 and npm 11.9.0, a one-line ES module in a project from npm init -y threw:

(node:18728) Warning: Failed to load the ES module: D:\nk-export\a\esm.js. Make sure to set "type": "module" in the nearest package.json file or use the .mjs extension.
export const a = 1; console.log('ok', a)
SyntaxError: Unexpected token 'export'

On Node 20 the warning before the error reads a little differently: To load an ES module, set "type": "module" in the package.json or use the .mjs extension. The Node version doesn’t matter; the "type" line does, with the same results as the table above on Node 20.20.2, 22.23.4, 24.22.1 and 26.13.0. Which npm made the package.json decides whether that line is there:

npm init -y with writes "type"
npm 10.8.2, 10.9.9 (bundled with Node 20 and 22) nothing
npm 11.9.0, 11.19.1, 11.20.0 (Node 24 and 26) "commonjs"
npm 12.2.1 "commonjs"

That’s why a project started on Node 22 can run an ES module file with no "type" at all, while the same steps on Node 24 throw. The fixes are the ones at the top: "type": "module" (the file then ran with no error and no warning), or rename just that file to .mjs, which ran inside a "type": "commonjs" project on all four versions.

In Jest, with an ESM-only dependency

Many packages now ship only ES modules (nanoid 5 is one). In a CommonJS project, Jest 30.5.2 on Node 24.14.0 refused to load it:

Must use import to load ES Module: D:\nk-export\a\node_modules\nanoid\index.js
The file contains ESM syntax (import/export) that could not be executed as CommonJS.

Jest’s own message suggests Node 24.9+ handles it natively, but it still failed on 24.14.0, and with Jest 30.5.2 it failed the same way on Node 20, 22, 24.22.1 and 26.13.0. Plain Node 22.12 and newer can require() an ES module (see ERR_REQUIRE_ESM), but Jest loads modules itself, so that doesn’t help inside a test.

Run Jest in ES module mode with the --experimental-vm-modules flag shown above, and use import in the test:

import { nanoid } from 'nanoid';
test('id', () => { expect(nanoid().length).toBe(21); });

Or keep require and let Babel transform that one package. Jest skips everything in node_modules by default, so tell it not to skip nanoid. Install babel-jest, @babel/core and @babel/preset-env, add the babel.config.json from the Jest section above, then:

// jest.config.js
module.exports = {
  transformIgnorePatterns: ['/node_modules/(?!nanoid)'],
};

If you can’t switch the tests to ES modules, use the dependency’s last CommonJS version instead. For nanoid that’s npm i nanoid@3.

In the browser

A file with export, loaded with a plain <script src="mod.js"></script>, printed this in Chromium’s console, and the script never ran:

Uncaught SyntaxError: Unexpected token 'export'

Why it happens

JavaScript has two loading modes. import and export only exist in ES modules. Browsers treat <script> without type="module" as a classic script. Node treats a file as CommonJS when package.json says so, when the file is .cjs, or on older Node, by default.

What didn’t work

How this was tested

September 2026: Node 24.14.0 and Chrome 152 on Windows 11 Pro. November 1, 2026: every "type" and extension combination, node -e, a Jest 30 test using import (no config, Babel, VM modules) and TypeScript 6.0.3 output with module set to esnext, nodenext and commonjs, on Node 20.20.2, 22.23.4, 24.22.1 and 26.13.0 on GitHub Actions ubuntu-latest. The script and the full output are public.

The Unexpected token 'export' cases: October 18, 2026 on Windows 11 Pro, Node 24.14.0, npm 11.9.0, with Jest 30.5.2 against nanoid 5.1.16 and 3, and the browser case served from a local HTTP server in Chromium (Claude’s built-in browser). November 1, 2026: the Node and Jest cases re-ran on Node 20.20.2, 22.23.4, 24.22.1 and 26.13.0 on GitHub Actions ubuntu-latest, adding the .cjs and .mjs renames, npm init -y on each bundled npm and on npm 12.2.1, and the Babel transformIgnorePatterns fix. That script and output are public too.

— N.K., end of entry No.001

Useful? Pass it on:Post on XFollow @EmotionalMatter

Related entries

  1. No.002

    Fix: require, __dirname not defined in ES module scope

    ReferenceError: require, __dirname or __filename is not defined in ES module scope. Why Node says it, and the fixes, tested on Node 24 and 26.

    > ReferenceError: __dirname is not defined in ES module scope

    FIXED3 min
  2. No.026

    Fix: Dynamic require of "react" / "fs" is not supported

    esbuild's ES module output throws this for a require() it left in the bundle. Four fixes tested on Node 24, plus the case only Cloudflare Workers hits.

    > throw Error('Dynamic require of "' + x + '" is not supported');

    FIXED3 min
  3. No.023

    Fix: TypeScript enum is not supported in strip-only mode

    Node's ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX for enum, parameter properties, namespace, export = and import =. Two ways to run it unchanged, both tested.

    > enum Color { Red, Green }

    FIXED4 min

Post card · Every Sunday

What broke this week.

One email on Sundays: the Node, TypeScript, Python and AI SDK releases that broke builds that week, each one reproduced, with a link to the fix that worked. Never shared, and you can leave any time.

— Nilay

or follow by RSSor on X

By subscribing you agree to the privacy note. One click to leave.

tip: paste the exact error text