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'
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
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 jestIn 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