SyntaxError: Cannot use import statement outside a module

Quick answer

Node is parsing a file that uses import as CommonJS, where import is not valid. Fix it by making the file an ES module — add "type": "module" to package.json or rename it to .mjs — or switch the import lines to require().

The exact error string

import express from 'express';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at wrapSafe (node:internal/modules/cjs/loader:1281:20)
    at Module._compile (node:internal/modules/cjs/loader:1321:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)

The stack frames mention cjs/loader — that is the tell. Node loaded your file through the CommonJS loader, which does not understand import. The error is a parse error: it fires before any of your code runs.

Node.js import error: ESM vs CommonJS explained

Node supports two module systems. CommonJS (the original) uses require() and module.exports. ES modules (the standard) use import and export. They are parsed differently, and Node must decide which one a file is before running it.

Node treats a file as an ES module only when one of these is true: the file ends in .mjs, or the nearest package.json has "type": "module". Otherwise a .js file is CommonJS — and writing import in a CommonJS file is the exact syntax error you are seeing.

Most common causes

Almost every occurrence of this Node.js import error traces back to one of these situations:

Fix 1: enable ES modules (recommended)

If your code is written with import/export, tell Node to treat it as ESM. The cleanest option for a whole project is the type field:

// package.json
{
  "name": "my-app",
  "type": "module",   // every .js file is now an ES module
  "main": "index.js"
}

For a single ESM file inside an otherwise-CommonJS project, rename just that file to .mjs — the extension overrides the package default for that one file:

mv server.js server.mjs
node server.mjs   # import now works, no package.json change needed

Once a file is ESM, remember that relative imports need a file extension (import x from './util.js') — the missing-extension case throws ERR_MODULE_NOT_FOUND instead.

Fix 2: stay on CommonJS with require()

If you would rather keep CommonJS — for example, a small script or an older codebase — convert the import statements to require():

// ❌ ESM syntax in a CommonJS file
import express from 'express';
import { readFile } from 'node:fs/promises';

// ✅ CommonJS equivalent
const express = require('express');
const { readFile } = require('node:fs/promises');

Note: some modern packages are ESM-only and cannot be require()d at all. If a package documents itself as pure ESM, you must switch your file to ESM (Fix 1) rather than convert to require().

Fix 3: TypeScript and ts-node

With TypeScript, the error usually means your tsconfig.json module setting and your runtime disagree. The compiler emits CommonJS by default, but a misconfigured loader can pass raw import to Node. Align them with the NodeNext settings:

// tsconfig.json
{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "target": "ES2022"
  }
}

// Run ESM TypeScript directly:
node --loader ts-node/esm src/index.ts
// (with "type": "module" in package.json)

The root cause is almost always a mismatch between tsconfig's module and package.json's type. Pick ESM or CommonJS and make both agree.

Fix 4: Jest tests

Jest runs in CommonJS by default, so importing an ESM dependency throws this error from inside node_modules. The usual fixes:

Fix 5: Vite, webpack, and bundler config files

A surprising number of cases come not from your app code but from a config file. Tools like Vite, webpack, Tailwind, and PostCSS load their config (vite.config.js, webpack.config.js, postcss.config.js) through Node, so the same CommonJS/ESM rules apply to the config itself. If your package.json has "type": "module" but the config uses module.exports, or the reverse — the config uses import while the project is CommonJS — the loader throws this error before your build even starts.

// Project is CommonJS (no "type": "module"), but the config uses ESM:
// vite.config.js
import { defineConfig } from 'vite';   // ❌ Cannot use import statement…
export default defineConfig({ /* … */ });

// Option A — match the project: use CommonJS in the config
const { defineConfig } = require('vite');
module.exports = defineConfig({ /* … */ });

// Option B — keep ESM syntax: force this one file to be ESM
// rename to vite.config.mjs  (works regardless of package.json type)

The rule of thumb: a config file must match the module system Node will parse it under. If you want ESM config syntax in a CommonJS project, give the file the .mjs extension; if you want to keep .js, write the config in the same style as your package.json type dictates. Note the inverse failure too — an ESM config that uses __dirname will then hit require is not defined, since __dirname does not exist in ESM.

Browser scripts

In the browser, the same error appears when a <script> using import is not marked as a module. Add type="module" to the tag:

<!-- ❌ import fails in a classic script -->
<script src="app.js"></script>

<!-- ✅ marks the script as an ES module -->
<script type="module" src="app.js"></script>

Debugging checklist

Frequently Asked Questions

What does Cannot use import statement outside a module mean?

It means a file written with ES module syntax (import ... from ...) is being parsed by Node as a CommonJS module, where import is not valid syntax. Node decides a file is an ES module only if it ends in .mjs, or the nearest package.json has "type": "module". Without one of those, Node treats .js files as CommonJS and rejects the import statement.

How do I fix it in a Node.js project?

Add "type": "module" to your package.json to make every .js file an ES module, or rename the specific file to .mjs. Once the file is treated as ESM, import works. If you instead want to stay on CommonJS, replace the import statements with require() calls.

Why does it happen with ts-node or when running TypeScript?

ts-node and the TypeScript compiler emit CommonJS by default, but if your tsconfig or runtime is misconfigured the import syntax can reach Node unparsed. Set module and moduleResolution to NodeNext (or use ts-node's ESM loader) so the import is either compiled to require() or run as real ESM. A mismatch between tsconfig's module setting and package.json's type field is the usual cause.

Why do I get it in Jest tests?

Jest runs in CommonJS by default, so importing an ES module dependency triggers this error. Fix it by transforming the module with babel-jest or ts-jest, adding the package to transformIgnorePatterns exceptions so node_modules ESM gets transpiled, or running Jest in its experimental ESM mode.

Should I use type:module or rename to .mjs?

Use "type": "module" when you want the whole project to be ESM — it is the cleaner long-term choice for new projects. Rename a single file to .mjs when you only need one ES module inside an otherwise CommonJS project, since the extension overrides the package default for that file alone.

What is the difference between this error and require is not defined?

They are mirror images. Cannot use import statement outside a module means ESM syntax is running in a CommonJS context. require is not defined in ES module scope is the opposite — CommonJS require() running in an ES module context. Both come from mixing the two module systems; the fix is to pick one consistently for the file.

Working with a JSON API?

Format and validate any JSON response in your browser — nothing is uploaded to a server.

JSON Formatter JSON Validator All Error References
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.