import or export statement in a .js file without "type": "module" in package.json, it throws SyntaxError: Cannot use import statement outside a module. The fastest fix is to add "type": "module" to package.json. To fix a single file without touching package.json, rename it to .mjs. To stay with CJS, replace import with require(). For browsers, add type="module" to the <script> tag.
What is this error?
Node.js has two module systems: CommonJS (CJS) — the original system using
require() and module.exports — and ES Modules (ESM)
— the JavaScript standard using import and export. By default, Node.js
treats .js files as CommonJS. When the parser encounters import or
export in a CJS context, it does not know how to handle these keywords and throws
a SyntaxError before any code runs.
This is a parse-time error, not a runtime error — Node.js refuses to start the file at all. The fix requires telling Node.js to parse the file as an ES module.
SyntaxError: Cannot use import statement outside a moduleSyntaxError: Cannot use import statement in a scriptSyntaxError: Unexpected token 'export'Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
Full Error Example
$ node src/index.js
/home/user/project/src/index.js:1
import express from 'express';
^^^^^^
SyntaxError: Cannot use import statement outside a module
at wrapSafe (node:internal/modules/cjs/loader:1394:18)
at Module._compile (node:internal/modules/cjs/loader:1416:20)
at Module._extensions..js (node:internal/modules/cjs/loader:1536:10)
at Module.load (node:internal/modules/cjs/loader:1275:32)
at Module._load (node:internal/modules/cjs/loader:1096:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/main/run_main_module:164:12)
at node:internal/main/run_main_module:28:49
The stack trace always points to the wrapSafe and Module._compile
internals in Node.js's CJS loader, confirming that the loader is trying to parse an ESM file
as CommonJS. The caret (^^^^^^) points at the import keyword on line 1.
CommonJS vs ES Modules — what Node.js decides
Node.js uses the following rules to decide which module system to use for a given file:
| File extension | Module system used | Notes |
|---|---|---|
.js |
Determined by nearest package.json "type" field |
Defaults to CJS if no "type" field or "type": "commonjs" |
.mjs |
Always ESM | Regardless of package.json |
.cjs |
Always CommonJS | Regardless of package.json |
.ts |
Depends on TypeScript compiler settings and runner | ts-node, tsx, or tsc output follows separate rules |
Common Causes
| Cause | Why it happens |
|---|---|
Using import in a .js file without "type": "module" |
The project has no "type" field in package.json, so Node.js uses CommonJS by default. Any import statement triggers the error. |
| Mixing ESM and CJS in the same project | Some files use import and others use require(). Node.js cannot load a CJS file that require()s a file with import syntax. |
| Running TypeScript directly without compilation | ts-node without ESM configuration compiles TypeScript to CJS by default. If the TypeScript source uses import and the output target is CJS, Node.js sees the compiled CJS output and the import in the original causes a parse error. |
Using import in a Jest test without ESM config |
Jest transforms test files to CJS by default using Babel or its own transformer. Without the correct transform config, import statements in test files cause this error. |
| Copying ESM code from documentation or examples | Modern JavaScript documentation, MDN, and npm package READMEs use ESM syntax. Pasting that code into a CJS project without adjusting the syntax or package.json causes the error. |
| A dependency updated to ESM-only | Popular packages like chalk, node-fetch, nanoid, and got dropped CJS support. If you require() them you get ERR_REQUIRE_ESM. If you write import to consume them in a CJS project, you get this error. |
Monorepo: missing "type": "module" in sub-package |
In workspaces (Yarn, pnpm, Nx, Turborepo), each sub-package has its own package.json. The root "type": "module" does not propagate to nested packages. Each package that uses ESM needs its own declaration. |
Browser: <script> tag without type="module" |
In a browser, a <script src="app.js"> tag loads the file as a classic script. Classic scripts do not support import. The error message in browser DevTools is: Uncaught SyntaxError: Cannot use import statement outside a module. |
Fix 1 – Add "type": "module" to package.json (recommended)
This is the cleanest fix if you want to use ES modules throughout the project.
It tells Node.js to treat all .js files in the project as ESM.
{
"name": "my-app",
"version": "1.0.0",
"type": "module",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js"
}
}
After adding "type": "module", your import statements will work:
// src/index.js — now valid ESM
import express from 'express';
import { readFile } from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
app.listen(3000);
"type": "module", all existing
require() calls in .js files will break with
ReferenceError: require is not defined in ES module scope.
You must either convert them all to import, rename CJS files to .cjs,
or keep a mixed setup. Also note that __dirname and __filename are
not available in ESM — use import.meta.url instead (see below).
Fix 2 – Rename the file to .mjs (single-file fix)
If you only need one file to use ESM without converting the entire project, rename it from
.js to .mjs. Node.js always treats .mjs as ESM,
regardless of the package.json type field.
# Rename the file
mv src/index.js src/index.mjs
# Update your start script in package.json
# "start": "node src/index.mjs"
// src/index.mjs — always treated as ESM by Node.js
import express from 'express';
const app = express();
app.listen(3000, () => console.log('Server running'));
The .mjs extension is ideal for standalone scripts, utilities, or tool configuration
files that use modern ESM syntax inside an otherwise CJS project.
Fix 3 – Convert import to require() (stay with CJS)
If you want to remain in CommonJS — for example because your toolchain requires it — replace
all import statements with require() calls.
| ESM (import) | CommonJS equivalent (require) |
|---|---|
import fs from 'fs' |
const fs = require('fs') |
import { readFile } from 'fs/promises' |
const { readFile } = require('fs/promises') |
import express from 'express' |
const express = require('express') |
import { foo } from './foo.js' |
const { foo } = require('./foo') |
import * as utils from './utils.js' |
const utils = require('./utils') |
export default function handler() {} |
module.exports = function handler() {} |
export const foo = 42 |
exports.foo = 42 or module.exports = { foo: 42 } |
// Before (ESM — fails in CJS context)
import express from 'express';
import { readFile } from 'fs/promises';
export default app;
// After (CommonJS — works without type:module)
const express = require('express');
const { readFile } = require('fs/promises');
module.exports = app;
import() function (note: this returns a Promise):
const mod = await import('./esm-module.mjs'). This works in any CJS file and is
the standard way to consume ESM-only packages from CJS.
Fix 4 – TypeScript with ts-node: enable ESM mode
When running TypeScript directly with ts-node, it compiles to CJS by default.
To use ESM import syntax end-to-end, enable ESM mode in one of three ways:
Option A: Command-line flag
# Run with ESM mode enabled
ts-node --esm src/index.ts
# Or via npx
npx ts-node --esm src/index.ts
Option B: tsconfig.json + package.json configuration
// tsconfig.json
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ES2022"
},
"ts-node": {
"esm": true
}
}
// package.json — also required
{
"type": "module"
}
Option C: Use tsx instead of ts-node
# tsx handles ESM TypeScript with zero configuration
npm install --save-dev tsx
# Run TypeScript ESM files directly
npx tsx src/index.ts
"module": "NodeNext",
TypeScript requires explicit file extensions in imports:
import { foo } from './foo.js' (not './foo'). The .js
extension resolves to the compiled output, which may be a .js or .mjs
file at runtime.
Fix 5 – Jest: configure ESM support
Jest transforms test files to CJS by default. To use ESM import syntax in tests,
you have two main options:
Option A: Native ESM with experimental-vm-modules
// jest.config.js
export default {
// Do not transform — let Node.js handle ESM natively
transform: {},
// Tell Jest to treat .js files as ESM
extensionsToTreatAsEsm: ['.ts'],
};
// package.json — add the experimental flag to the test script
{
"type": "module",
"scripts": {
"test": "node --experimental-vm-modules node_modules/.bin/jest"
}
}
Option B: Babel transform (stay with CJS, but write ESM syntax)
// babel.config.cjs
module.exports = {
presets: [
['@babel/preset-env', {
targets: { node: 'current' },
modules: 'auto', // transforms import to require for Jest
}],
],
};
# Install required packages
npm install --save-dev @babel/core @babel/preset-env babel-jest
Fix 6 – Babel transform for CJS output
If you have an existing CJS project that uses Babel (common in older Node.js projects and some
frameworks), configure Babel to transform ESM syntax to CJS. This lets you write modern
import/export syntax while Babel compiles it to require()
at build time.
// babel.config.json
{
"presets": [
[
"@babel/preset-env",
{
"targets": { "node": "18" },
"modules": "commonjs"
}
]
]
}
# Build step: transform ESM source to CJS output
npx babel src --out-dir dist --extensions '.js,.mjs'
# Run the CJS output
node dist/index.js
Fix 7 – Next.js: transpilePackages for ESM dependencies
Next.js uses its own SWC/Webpack-based bundler and handles ESM internally. You should
not add "type": "module" to a Next.js project's root
package.json — it can break the build. If you see this error because an npm
dependency is ESM-only, add it to the transpilePackages array:
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ['some-esm-only-package', 'another-esm-package'],
};
module.exports = nextConfig;
For Next.js App Router (Next.js 13+) server components and API routes, ensure that server-side
files do not mix require() calls and import statements. If you need
to use a CJS module inside a server component, use dynamic import():
// Inside an async Next.js Server Component
const { someFunction } = await import('cjs-only-package');
Fix 8 – NestJS: configure module format
NestJS projects generated by the CLI default to CommonJS. If you paste ESM import
syntax or install an ESM-only dependency, you will hit this error. The recommended approach for
NestJS is to stay with CJS and use the esm npm package for consuming ESM-only
packages, or switch to using dynamic import():
// In a NestJS service: consume an ESM-only package from CJS
@Injectable()
export class MyService {
async doWork() {
// dynamic import works in CJS context
const { default: someEsmModule } = await import('esm-only-package');
return someEsmModule.run();
}
}
If you want full ESM in NestJS, you can configure the tsconfig.json with
"module": "NodeNext" and add "type": "module" to package.json,
but this requires careful adjustment of all import paths to use explicit .js extensions.
Fix 9 – Browser: add type="module" to the script tag
In a browser environment, a classic <script> tag does not support ES module
import syntax. The browser DevTools will show:
Uncaught SyntaxError: Cannot use import statement outside a module.
Fix it by adding the type="module" attribute:
<!-- Before: classic script — import fails -->
<script src="app.js"></script>
<!-- After: module script — import works -->
<script type="module" src="app.js"></script>
Module scripts are automatically deferred (they run after the document is parsed), are
subject to CORS restrictions (you cannot load them from file:// without a server),
and each execute in their own module scope (no global variable pollution).
<script type="module">import { foo } from './foo.js'; foo();</script>
Fix 10 – Vite and Webpack: bundler configuration
Modern bundlers like Vite and Webpack handle both ESM and CJS. This error typically appears during server-side code execution (not during the bundle build itself).
Vite
Vite natively supports ESM. If you see this error in a Vite project, it usually comes from
server-side code (SSR) or a Node.js script outside of Vite's build pipeline. Ensure your
vite.config.js uses ESM syntax or is renamed to vite.config.mjs:
// vite.config.mjs — always treated as ESM
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
ssr: {
// Force SSR dependencies to be bundled as ESM
noExternal: ['some-esm-package'],
},
});
Webpack
Webpack can consume ESM source files regardless of the project's CJS/ESM mode. If you run a
Node.js script (not Webpack itself) that uses import, you still need
"type": "module". For Webpack config files, rename to webpack.config.mjs
or use require() syntax in webpack.config.js:
// webpack.config.mjs — using ESM syntax in Webpack config
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
};
Fix 11 – Electron: main process and preload scripts
Electron's main process and preload scripts run in a Node.js CJS environment by default.
If you write import statements in these files, you get this error.
You have three options:
- Stay with CJS: Use
require()in main process and preload scripts. - Use
.mjsextension: Renamemain.jstomain.mjsand update the"main"field inpackage.json. - Use a bundler: Configure Electron Forge or electron-builder with Vite or Webpack to bundle ESM source to CJS output for the main process.
// electron-forge with Vite plugin — vite.main.config.mjs
import { defineConfig } from 'vite';
export default defineConfig({
build: {
lib: {
entry: 'src/main.ts',
formats: ['cjs'], // Electron main process must be CJS
},
rollupOptions: {
external: ['electron'],
},
},
});
Fix 12 – Monorepo / workspace: per-package type declaration
In monorepos using Yarn workspaces, pnpm workspaces, Nx, or Turborepo, the "type"
field in package.json only applies to files in that package's directory. The root
package.json type does not cascade to sub-packages. Each package that uses ESM
needs its own "type": "module":
monorepo/
├── package.json ← root (type may or may not be set)
├── packages/
│ ├── api/
│ │ ├── package.json ← { "type": "module" } — API uses ESM
│ │ └── src/index.js
│ └── utils/
│ ├── package.json ← { "type": "commonjs" } — utils uses CJS
│ └── src/index.js
// packages/api/package.json
{
"name": "@myorg/api",
"type": "module",
"main": "src/index.js"
}
__dirname and __filename in ES modules
After switching to ESM with "type": "module", you will find that __dirname
and __filename throw ReferenceError: __dirname is not defined in ES module scope.
These CommonJS globals do not exist in ESM. Replace them with:
import { fileURLToPath } from 'url';
import path from 'path';
// ESM equivalents of __filename and __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Usage example
const configPath = path.join(__dirname, 'config.json');
File extension comparison
| Extension | Module system | Requires "type": "module" |
Best for |
|---|---|---|---|
.js with "type": "module" |
ESM | Yes | Full ESM projects — all files use import/export |
.mjs |
Always ESM | No | Individual ESM files inside a CJS project |
.cjs |
Always CJS | No (opt-out) | Individual CJS files inside a full ESM project |
.js (no type field) |
CJS | No | Traditional CJS projects — all files use require() |
Relationship with ERR_REQUIRE_ESM
SyntaxError: Cannot use import statement outside a module and
ERR_REQUIRE_ESM are the
opposite sides of the same ESM/CJS boundary problem:
| Error | What happened | Fix |
|---|---|---|
SyntaxError: Cannot use import statement outside a module |
You wrote ESM import syntax in a file Node.js is loading as CJS |
Add "type": "module", rename to .mjs, or use require() |
ERR_REQUIRE_ESM |
You called require() on a file or package that is pure ESM |
Switch to import or use dynamic import() from CJS |
When you add "type": "module" to fix the SyntaxError, you may then encounter
ERR_REQUIRE_ESM if any code in your project still uses require().
The solution is to convert all require() calls to import statements,
or rename those specific files to .cjs.
Debugging Checklist
- Check the first line of the error stack trace — it shows exactly which file contains the
importstatement that caused the error. - Check your
package.json— is"type": "module"present? If not, Node.js defaults to CJS. - In a monorepo, check the
package.jsonin the sub-package containing the file, not just the root. - Check the file extension — is it
.js,.mjs, or.cjs? Only.mjsis always ESM. - If using
ts-node, confirm you are running with--esmor have"esm": trueintsconfig.json. - If using Jest, confirm your
jest.confighas the correcttransformand you are running with--experimental-vm-modules. - If using Next.js, do not add
"type": "module"to the rootpackage.json; usetranspilePackagesinstead. - Check if the error comes from a dependency — some packages ship both CJS and ESM builds. Check the package's
package.json"exports"field to see which build is being loaded. - In a browser, ensure the
<script>tag hastype="module".
# Quick diagnostic: check if package.json has type:module
node -e "const p = require('./package.json'); console.log('type:', p.type || '(not set, defaults to commonjs)')"
# Check what module system Node.js will use for a specific file
node --input-type=module -e "console.log('ESM works')"
# List which version of Node.js you are running (ESM support: 12+, stable: 14+)
node --version
import
is not supported at all — upgrade to a supported LTS version. For older projects stuck on
Node.js 12, use the esm npm package as a loader:
node -r esm src/index.js.
Frequently Asked Questions
What causes SyntaxError: Cannot use import statement outside a module?
Node.js defaults to CommonJS (CJS) mode for .js files. When it encounters an import or export statement in a CJS context, it throws this error at parse time before any code executes. The root cause is always a mismatch: the file uses ESM syntax but Node.js is loading it as CommonJS.
Common triggers: no "type": "module" in package.json, running a .js file with ESM imports, using ts-node without ESM mode, or running Jest without an ESM transform.
How do I fix SyntaxError: Cannot use import statement outside a module?
The fastest fixes are:
1. Add "type": "module" to package.json to enable ESM for all .js files.
2. Rename the file from .js to .mjs for a per-file fix.
3. Replace import x from 'y' with const x = require('y') to stay in CJS.
4. For browsers, add type="module" to the <script> tag.
What is SyntaxError: Unexpected token 'export' in Node.js?
SyntaxError: Unexpected token 'export' is the same underlying problem. Node.js is parsing the file as CommonJS and encounters export — a keyword only valid in ESM. The parser does not recognize it and throws a SyntaxError. The fix is identical: add "type": "module" to package.json or rename the file to .mjs.
What is the difference between this error and ERR_REQUIRE_ESM?
They are mirror-image problems at the ESM/CJS boundary:
SyntaxError: Cannot use import statement outside a module — you used ESM import syntax in a file Node.js treats as CJS. Fix: add "type": "module", rename to .mjs, or use require().
ERR_REQUIRE_ESM — you called require() on a file or package that only ships ESM. Fix: switch to import or use await import() inside an async function.
How do I use __dirname and __filename in an ES module?
__dirname and __filename are CJS globals and do not exist in ESM. After switching to "type": "module" they throw ReferenceError: __dirname is not defined in ES module scope. The ESM replacement is:
import { fileURLToPath } from 'url';
import path from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
These give you exactly the same values as the CJS globals.
How do I fix this error in a monorepo or workspace?
In monorepos (Nx, Turborepo, Yarn workspaces, pnpm workspaces), the "type" field in package.json only applies to the directory it lives in — it does not cascade to nested packages. Each sub-package that uses ESM needs its own "type": "module" in its own package.json. Check the package.json in the same directory as the file that is throwing the error, not the root monorepo package.json.
How do I fix this error in a Next.js project?
Do not add "type": "module" to a Next.js root package.json — this can break the Next.js build. Instead, if an ESM-only dependency causes the error, add it to transpilePackages in next.config.js:
module.exports = {
transpilePackages: ['some-esm-only-package'],
};
For server components that need to use a CJS module, use dynamic import() inside an async function.
Related Errors
ERR_REQUIRE_ESM— the reverse problem: usingrequire()to load a pure ESM packageERR_PACKAGE_PATH_NOT_EXPORTED— importing a subpath not listed in the package's"exports"mapError: Cannot find module— module path cannot be resolved by the loaderReferenceError: require is not defined in ES module scope— usingrequire()in an ESM file; add.cjsextension or remove"type": "module"ReferenceError: __dirname is not defined in ES module scope—__dirnameand__filenamedo not exist in ESM; useimport.meta.urlwithpath.dirname(fileURLToPath(import.meta.url))