TypeScript

SyntaxError: Unexpected token import means an old Node.js

Read the exact wording of the error to learn which runtime ran your code, then choose between upgrading Node and staying on CommonJS with a pinned dependency.

Mahdi Ben RhoumaMahdi Ben Rhouma
7 min read

TL;DR#

SyntaxError: Unexpected token import is the wording of an old Node.js. The same file gives a different message on newer releases, so this wording tells you which runtime ran your code: in the reproduction below, only Node 8.17.0 printed it. That runtime loads every .js file as CommonJS, and a "type": "module" field in package.json does nothing there. The real fix is to run the code on a current Node.js release. If the runtime is fixed (an embedded Node, a pinned legacy image), write require() in your own code and pin each dependency to a release that still ships CommonJS: on Node 8, axios 0.27.2 loads where axios 1.7.7 throws this error.

The error, reproduced on 24 October 2026 with the official node:8-alpine Docker image (Node 8.17.0) and a two-line app.js that starts with import express from 'express':

text
/tmp/app/app.js:1
(function (exports, require, module, __filename, __dirname) { import express from 'express'
                                                              ^^^^^^
 
SyntaxError: Unexpected token import
    at createScript (vm.js:80:10)
    at Object.runInThisContext (vm.js:139:10)
    at Module._compile (module.js:617:28)
    at Object.Module._extensions..js (module.js:664:10)
    at Module.load (module.js:566:32)
    at tryModuleLoad (module.js:506:12)
    at Function.Module._load (module.js:498:3)
    at Function.Module.runMain (module.js:694:10)
    at startup (bootstrap_node.js:204:16)
    at bootstrap_node.js:625:3

Why only an old runtime says this#

The Stack Overflow question behind this page was asked in 2016 on Node v5.6.0, with the same function (exports, require, module, __filename, __dirname) wrapper in front of the import line. That wrapper is the CommonJS loader: Node wraps the file's text in a function and compiles it as a script, and a script cannot contain an import declaration.

What changed since 2016 is the wording, not the rule. The same app.js, with no package.json next to it, run on four official images:

text
v8.17.0
SyntaxError: Unexpected token import
v10.24.1
SyntaxError: Unexpected identifier
v12.17.0
SyntaxError: Cannot use import statement outside a module
v22.23.3
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'express' imported from /w/app.js

On Node 22.23.3 the syntax error is gone: the ECMAScript modules documentation explains that when a file has no explicit marker, "Node.js will inspect the source code of a module to look for ES module syntax", and runs it as an ES module if it finds any. What remains is only the missing express package. So a reader who pastes Unexpected token import into a search engine today is not on a recent Node.js, whatever their terminal says.

Two 2024 reports show who that reader is:

  • An Android app built with nodejs-mobile, in nodejs-mobile-samples issue #54 (November 2024): require('axios') failed inside node_modules/axios/index.js with SyntaxError: Unexpected token import, and the trace has the same createScript (vm.js:80:10) and Object.runInThisContext (vm.js:139:10) frames as the Node 8.17.0 run above. The asker writes "I had updated my node version and installed the axios by npm", yet the trace still shows those old loader frames, so the runtime that executed the app was not the updated one.
  • An old Android System WebView, in create-capacitor-app issue #24 (April 2024): WebView 60 and 61 logged Uncaught SyntaxError: Unexpected token import on an .esm.js file, while WebView 63 worked. This browser case was not reproduced here.

The main fix in the accepted answer, "type": "module" in package.json, does not reach this reader. The Node.js documentation history lists "Add support for ES modules using .js file extension via package.json "type" field" under v12.0.0, and the ES module loader itself as "Added in: v8.5.0". On Node 8.17.0, with that field set and a local lib.js import, the error did not move:

text
{ "name": "app", "version": "1.0.0", "type": "module" }
+ node app.js
/tmp/app/app.js:1
(function (exports, require, module, __filename, __dirname) { import { add } from './lib.js'
                                                              ^^^^^^
 
SyntaxError: Unexpected token import

Adding --experimental-modules to a .js file only changed the token:

text
+ node --experimental-modules app.js
(node:18) ExperimentalWarning: The ESM module loader is experimental.
/tmp/app/app.js:1
(function (exports, require, module, __filename, __dirname) { import { add } from './lib.js'
                                                                     ^
 
SyntaxError: Unexpected token {

When the import is inside a dependency#

Your own code can be pure CommonJS and still hit this error, which is the nodejs-mobile case: a dependency declares an ES module as its main file. With axios 1.7.7, whose package.json has main set to index.js and type set to module, a one-line const axios = require('axios') on Node 8.17.0 fails inside the package:

text
/tmp/app/node_modules/axios/index.js:1
(function (exports, require, module, __filename, __dirname) { import axios from './lib/axios.js';
                                                              ^^^^^^
 
SyntaxError: Unexpected token import
    at createScript (vm.js:80:10)
    at Object.runInThisContext (vm.js:139:10)
    at Module._compile (module.js:617:28)
    at Object.Module._extensions..js (module.js:664:10)
    at Module.load (module.js:566:32)
    at tryModuleLoad (module.js:506:12)
    at Function.Module._load (module.js:498:3)
    at Module.require (module.js:597:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (/tmp/app/demo.js:1:77)

The first line of the trace names the file that holds the import. If it sits under node_modules, the fix belongs to the dependency version, not to your code.

Fix#

  1. Find the runtime that actually ran the code. In a shell, run:

    bash
    node -v

    On a server or in CI, run the same command in the same container or job that fails. When Node is embedded in an app, as with nodejs-mobile, compare the trace with yours: if it still shows the module.js frames above after you upgraded Node on your computer, the app is running a different runtime.

  2. If you can change the runtime, move to a current Node.js release. That is the fix that keeps every current package working. The same require('axios') with axios 1.7.7 printed function on Node 12.17.0, 14.21.3 and 22.23.3. On a current release you can keep import in your own code by using an .mjs extension or "type": "module", as the Node.js documentation describes. In a Docker image, change the base tag, for example:

    dockerfile
    FROM node:22-alpine
  3. If the runtime cannot change, write CommonJS in your own files, as the accepted answer says:

    js
    const express = require('express');
  4. Pin the dependency named in the trace to a release whose main file is CommonJS. For axios on Node 8.17.0, 0.27.2 loaded:

    bash
    npm install axios@0.27.2

    Do not go back to latest on that runtime: axios 1.20.0 loads ./dist/node/axios.cjs through main, and that file fails on Node 8 with a different syntax error (see below).

  5. To keep import syntax on a Node 8.5 or later runtime you cannot upgrade, the experimental loader accepts .mjs files. It is a stopgap: it runs only behind the flag and prints a warning. With an app.mjs that imports ./lib.mjs:

    bash
    node --experimental-modules app.mjs

Verify the fix#

Run the file that failed again, on the same runtime. With axios pinned to 0.27.2 on Node 8.17.0, the demo.js from above printed:

text
0.27.2
+ node demo.js
function

The .mjs route printed its result, after the loader's warning:

text
+ node --experimental-modules app.mjs
(node:10) ExperimentalWarning: The ESM module loader is experimental.
5

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

The same require, five Node releases, four outcomes#

The axios 1.7.7 install is a useful probe, because the outcome changes with the runtime. Run on the official images, node demo.js gave:

text
v10.24.1
SyntaxError: Unexpected identifier
v12.6.0
SyntaxError: Unexpected identifier
v12.16.3
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /tmp/app/node_modules/axios/index.js
v12.17.0
function

So Unexpected identifier on Node 10 or an early Node 12 is the same problem with newer wording, and the fixes above apply to it unchanged. On 12.16.3 the error became ERR_REQUIRE_ESM, and on 12.17.0 the same install loaded.

When the error changes after the fix#

If you pin a newer release of a package than the runtime can parse, the import error goes away and a parse error takes its place. On Node 8.17.0 with axios 1.20.0:

text
/tmp/app/node_modules/axios/dist/node/axios.cjs:2658
  } catch {
          ^
 
SyntaxError: Unexpected token {

That catch without a binding is newer syntax than Node 8 knows. The cause is the one behind tsc's Unexpected token '?' on old Node.js: the package is newer than the runtime, and the fix is an older package or a newer Node.

After you move to a current release and switch your files to ES modules with "type": "module", any file that still writes module.exports or exports.foo stops working; that case is covered in exports is not defined in ES module scope. If a tool loads your files through its own CommonJS step, the message you see on Node 12 and later, until the releases that can require() ES modules (the linked page gives them), is Cannot use import statement outside a module, which is how older drizzle-kit releases fail on an ES module in the schema.

The exact wording of the error is the diagnosis here. Unexpected token import says the code met a Node.js release that predates the module tooling of the last few years, so changing package.json is the wrong lever. Upgrade the runtime when you can; when you cannot, keep that runtime on require() and on dependency versions it can parse, and check the first line of each trace to see which package needs pinning.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote