Node "Cannot use import statement outside a module" in CI - Fix It
By Kaveh Alemi·Latchkey
This SyntaxError means Node parsed a file as CommonJS but the file uses ES import syntax. The runtime never reaches your logic; parsing fails first.
What this error means
A node command throws SyntaxError: Cannot use import statement outside a module at the first import line. The file uses import/export but Node is loading it as CommonJS.
node
import express from 'express';
^^^^^^
SyntaxError: Cannot use import statement outside a module
at internalCompileFunction (node:internal/vm:73:18)
Diagnose it: the shell in CI is not your shell
Package scripts run under a different shell, a different PATH, and a non-interactive environment on a runner. Most scripts that fail only in CI are relying on something the login shell gave them locally: a tool on PATH, an environment variable from a dotfile, or a TTY.
Terminal
# what the script can actually see
npm run env | grep -E "^(PATH|NODE_ENV|CI)="
# is the binary on PATH for the script, not just for you?
npm exec -- which <tool> || echo "not resolvable from npm scripts"
# run the exact script with tracing
sh -x -c "$(node -p "require('./package.json').scripts.build")"
Common causes
No "type": "module" in package.json
A .js file with import syntax is parsed as CommonJS unless the nearest package.json declares type module.
Running uncompiled TypeScript or JSX directly
Node runs raw source that should have passed through a build or loader (tsc, tsx, babel) but did not.
How to fix it
Declare the package as ESM
Add "type": "module" to package.json so .js files are treated as ES modules.
Make sure your require() calls are converted to import or moved to .cjs files.
package.json
{
"type": "module"
}
Transpile before running
Run the build (tsc, esbuild, babel) so the emitted CommonJS no longer uses import syntax.
Execute the compiled output instead of the source.
GitHub Actions
- run: npm run build
- run: node dist/server.js
Make failures fail the job
A multi-command script can report success while a middle command failed, which produces the worst kind of CI result: a green build that shipped something broken.
.github/workflows/ci.yml
# pipefail is NOT set by default in every runner shell- name:Buildshell:bashrun:|set -euo pipefailnpm run build | tee build.log
How to prevent it
Pick one module system per package, set the matching "type" field, and run compiled output in CI rather than raw source that depends on a loader being present.
Frequently asked questions
What causes Node "Cannot use import statement outside a module" in CI?
There are 2 common causes: no "type": "module" in package.json and running uncompiled typescript or jsx directly. A .js file with import syntax is parsed as CommonJS unless the nearest package.json declares type module.
How do I fix Node "Cannot use import statement outside a module" in CI?
There are 2 fixes depending on which cause you have: declare the package as esm and transpile before running. Work through them in order, since the first is the most common.
What does Node "Cannot use import statement outside a module" in CI actually mean?
A node command throws SyntaxError: Cannot use import statement outside a module at the first import line.
How do I stop Node "Cannot use import statement outside a module" in CI happening again?
Pick one module system per package, set the matching "type" field, and run compiled output in CI rather than raw source that depends on a loader being present.