Skip to content
Latchkey

Node.js "__dirname is not defined in ES module scope" in CI

ESM does not define __dirname or __filename. Code that relied on those CommonJS globals throws a ReferenceError once the file runs as an ES module.

What this error means

After enabling "type": "module" (or running a built ESM bundle), any use of __dirname/__filename fails in CI with a ReferenceError.

node
ReferenceError: __dirname is not defined in ES module scope
    at file:///work/repo/dist/config.js:4:21

Diagnose it: what is different about the runner?

A build that passes locally and fails on a runner differs in a small number of predictable ways. Check those before changing build configuration, because the build config is usually not the thing that changed.

.github/workflows/ci.yml
- run: |
    node --version && npm --version
    echo "NODE_ENV=$NODE_ENV  CI=$CI"
    nproc && free -h && df -h /
    ls -la node_modules/.bin | head

Common causes

CommonJS globals removed in ESM

ESM intentionally omits __dirname/__filename. Any code or dependency-adjacent helper that uses them breaks under ESM.

How to fix it

Derive the directory from import.meta.url

Reconstruct __dirname using fileURLToPath and path.dirname.

src/config.js
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';

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

Use import.meta.dirname on newer Node

Recent Node versions expose import.meta.dirname and import.meta.filename directly.

src/config.js
const here = import.meta.dirname; // Node 20.11+ / 21.2+

The three that account for most of them

  • Case sensitivity. Linux runners are case sensitive, macOS is not. An import with the wrong case resolves locally and fails in CI.
  • Out of memory. Exit code 137 is a SIGKILL from the kernel, not a build error. Raise --max-old-space-size or use a larger runner.
  • devDependencies pruned. NODE_ENV=production makes npm ci skip devDependencies, so the build tool itself goes missing. Set it after install, not before.

How to prevent it

  • Add the fileURLToPath shim once in a shared module when migrating to ESM.
  • Pin a Node version in CI that matches the import.meta features you use.
  • Grep for __dirname/__filename before flipping a package to "type": "module".

Frequently asked questions

What causes Node.js "__dirname is not defined in ES module scope" in CI?
commonjs globals removed in esm. ESM intentionally omits __dirname/__filename.
How do I fix Node.js "__dirname is not defined in ES module scope" in CI?
There are 2 fixes depending on which cause you have: derive the directory from import.meta.url and use import.meta.dirname on newer node. Work through them in order, since the first is the most common.
What does Node.js "__dirname is not defined in ES module scope" in CI actually mean?
After enabling "type": "module" (or running a built ESM bundle), any use of __dirname/__filename fails in CI with a ReferenceError.
How do I stop Node.js "__dirname is not defined in ES module scope" in CI happening again?
Add the fileURLToPath shim once in a shared module when migrating to ESM. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card