Skip to content
Happy Programming Guide
Start learning
Debugging & Errors

How to Fix “SyntaxError: Cannot use import statement outside a module”

This JavaScript error means the file is being read as a script, not a module. Here are the fixes for Node, the browser, TypeScript and Jest.

A red ladybird on a leaf in the sun

SyntaxError: Cannot use import statement outside a module means JavaScript found an import in a file it is treating as an old-style script. The fix depends on where the code runs: in Node you set the module type, in the browser you add type="module", and in a test runner you configure the transform. All four are below.

Why this happens#

JavaScript has two module systems. CommonJS came first and uses require() and module.exports; it is what Node used for years. ES modules are the newer standard and use import and export.

A file is one or the other, never both, and the runtime has to decide which before it parses a single line. When it guesses CommonJS and then meets import, you get this error.

Fix 1: Node#

Add "type": "module" to your package.json:

JSON
{
  "name": "my-app",
  "version": "1.0.0",
  "type": "module",
  "main": "index.js"
}

Every .js file in the project is now treated as an ES module. If you have no package.json, create one with npm init -y and add the line.

For a single file without touching the project, rename it:

Terminal
mv script.js script.mjs
node script.mjs

.mjs always means ES module; .cjs always means CommonJS, whatever the package.json says.

What changes when you switch#

ES modules are stricter in a few ways that catch people out:

JavaScript
// File extensions are required for local imports
import { helper } from "./utils.js";     // correct
import { helper } from "./utils";        // ERR_MODULE_NOT_FOUND

// __dirname and __filename do not exist
import { fileURLToPath } from "url";
import path from "path";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// JSON needs an import attribute
import data from "./data.json" with { type: "json" };

You can still load a CommonJS package from an ES module, because the default export is the whole module.exports object:

JavaScript
import express from "express";   // works fine

Fix 2: the browser#

HTML
<!-- Fails: import in a classic script -->
<script src="app.js"></script>

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

Two consequences of type="module" that surprise people:

  • Module scripts are deferred automatically, so they run after the HTML is parsed. You no longer need to wrap everything in a DOMContentLoaded handler.
  • Modules are subject to CORS, so opening the page with file:// fails. Serve it over HTTP: npx serve or python -m http.server.

Inline modules work too:

HTML
<script type="module">
  import { greet } from "./greet.js";
  greet();
</script>

Fix 3: TypeScript#

TypeScript compiles import to whatever module says. If the output is ES modules but Node expects CommonJS, you get this error at runtime rather than at compile time.

JSON
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "dist"
  }
}

NodeNext tells TypeScript to follow the same rules Node does, using your package.json type field. That keeps the two in agreement, which is the actual fix.

For CommonJS output instead, set "module": "CommonJS" and leave "type": "module" out of package.json.

Fix 4: Jest#

Jest historically ran in CommonJS, so importing an ES-module-only package fails. The straightforward route is Babel:

Terminal
npm install --save-dev babel-jest @babel/core @babel/preset-env
JavaScript
// babel.config.cjs
module.exports = {
  presets: [["@babel/preset-env", { targets: { node: "current" } }]],
};

Alternatively, run Jest in native ESM mode:

JSON
{
  "scripts": {
    "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
  }
}

Vitest is worth considering if you keep fighting this — it supports ES modules natively and its API is close enough that most Jest tests need few changes.

You cannot mix the two in one file#

JavaScript
import fs from "fs";
const path = require("path");     // ReferenceError: require is not defined

Pick one per file. If you genuinely need a CommonJS-only package inside an ES module:

JavaScript
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const legacy = require("some-old-package");

Quick diagnosis#

Where it happens Most likely fix
node script.js Add "type": "module", or rename to .mjs
Browser console Add type="module" to the script tag
After tsc Set module to NodeNext
Only in tests Configure babel-jest, or switch to Vitest
Only in one dependency That package is ESM-only; use dynamic import()

Questions people ask#

Should new projects use ES modules?

Yes. It is the standard, it works in browsers without a build step, and the ecosystem has largely moved. CommonJS is for maintaining existing code.

Why does the error mention “outside a module” when my file is a module?

Because the runtime disagrees. What matters is not what you intended but how the file was classified — by extension, by package.json, or by the script tag.

Can I import an ES-only package from CommonJS?

Not with require(). Use dynamic import, which returns a promise: const mod = await import("the-package"). That works inside an async function in CommonJS.

What is the difference between .mjs and .cjs?

They force the classification regardless of package.json. .mjs is always an ES module, .cjs is always CommonJS. Useful for one-off files and during a migration.

Where to go next#

Exporting functions in JavaScript, named vs defaultRead next

Keep reading

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *