dirname Is Not Defined in ES Module Scope — Node.js Fix Guide

Getting ReferenceError: __dirname is not defined in ES module scope in Node.js? This guide explains why it happens and shows you every fix — from fileURLToPath to import.meta.dirname.

dirname Is Not Defined in ES Module Scope


You’re running a Node.js project that uses ES modules, and you get this:

ReferenceError: __dirname is not defined in ES module scope

The __dirname is not defined in ES module error is one of the most common stumbling blocks when migrating from CommonJS to ES modules, or when starting a new Node.js project with "type": "module" in package.json. It’s not a bug — it’s a deliberate design decision in how ES modules work. Once you understand the difference, the fix is straightforward.


Why __dirname Exists in CommonJS But Not in ES Modules

In Node.js’s traditional CommonJS system, every module gets several globals injected automatically:

  • __dirname: the absolute path of the directory containing the current file
  • __filename: the full absolute path of the current file
  • require: the function to import other modules
  • module and exports: for exporting values

When Node.js added native ES module support (using import and export), the team made a conscious choice not to carry these CommonJS-specific globals over. The reason is that ES modules are designed to be a universal JavaScript standard that works in both Node.js and browsers. Browsers don’t have a file system, so __dirname and __filename have no meaning there.

Instead, ES modules get import.meta, a special object that provides context-specific information about the current module. In Node.js, import.meta.url gives you the current module’s URL as a string, like:

file:///Users/username/projects/myapp/server.js

That’s different from a file path, but it’s what you have to work with. Everything that follows is about converting that URL into what you actually want.


How Your Project Gets Into ES Module Mode

You’ll hit this error in one of two situations:

  1. Your package.json has "type": "module":
json
{
  "name": "myapp",
  "type": "module"
}

This tells Node.js to treat all .js files as ES modules.

  1. Your file has a .mjs extension:

Node.js treats .mjs files as ES modules regardless of package.json settings.

In either case, __dirname doesn’t exist in the module scope and you’ll see the ReferenceError the moment you try to use it.


Fix 1: Recreate __dirname Using import.meta.url (Works Everywhere)

This is the standard fix that works across all supported Node.js versions. Two imports are needed — fileURLToPath from the url module and dirname from the path module:

js
import { fileURLToPath } from 'url';
import { dirname } from 'path';

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

console.log(__dirname);   // /Users/username/projects/myapp
console.log(__filename);  // /Users/username/projects/myapp/server.js

Here’s what each line does:

  • import.meta.url gives you the current file as a URL string (file:///...)
  • fileURLToPath() converts that URL into a normal file path string
  • dirname() extracts the directory from that file path

After these two lines, __dirname and __filename behave exactly as they do in CommonJS. You can use path.join(__dirname, 'public') or any other path operation as normal.

This is the approach recommended by the Node.js documentation and is compatible with Node.js 12 and later.


Fix 2: Use import.meta.dirname (Node.js 20.11+ Only)

Starting from Node.js version 20.11.0, you can skip the boilerplate entirely:

js
// Node.js 20.11+ only
console.log(import.meta.dirname);   // direct __dirname equivalent
console.log(import.meta.filename);  // direct __filename equivalent

No imports needed. These properties are built directly into import.meta in newer Node.js versions.

Version availability:

  • Node.js 20.11+ → import.meta.dirname and import.meta.filename are available
  • Node.js 18 and earlier → not supported, use the fileURLToPath approach instead

Check your Node.js version with node --version before relying on this. If you’re targeting multiple environments or older Node.js releases, the fileURLToPath approach in Fix 1 is safer.


Fix 3: Use the new URL() Pattern Directly

For reading files relative to the current module, you can bypass __dirname entirely using the URL constructor:

js
import { readFile } from 'fs/promises';

// Read config.json relative to the current module
const config = JSON.parse(
  await readFile(new URL('./config.json', import.meta.url), 'utf8')
);

new URL('./config.json', import.meta.url) creates a fully-resolved URL for a file next to the current module. Many fs functions accept URL objects directly, so you don’t need to convert to a string path at all.

This is the most ESM-native approach and works well for simple file loading.


Fix 4: Revert to CommonJS If You Don’t Need ES Modules

If you added "type": "module" to package.json but don’t specifically need ES module features, removing it is a valid option:

json
{
  "name": "myapp"
}

Without "type": "module", Node.js defaults back to CommonJS for .js files, and __dirname works again without any changes.

Alternatively, rename your files from .js to .cjs. Node.js always treats .cjs files as CommonJS regardless of the package.json setting:

bash
mv server.js server.cjs

This gives you a path to use CommonJS in specific files while keeping the rest of your project on ES modules.


Creating a Shared Helper to Avoid Repetition

If you need __dirname in multiple files, recreating the two-liner in every file gets tedious. A shared utility module keeps things clean:

js
// utils/paths.js
import { fileURLToPath } from 'url';
import { dirname } from 'path';

export function getDirname(importMetaUrl) {
  return dirname(fileURLToPath(importMetaUrl));
}

export function getFilename(importMetaUrl) {
  return fileURLToPath(importMetaUrl);
}

Then in any file that needs it:

js
// server.js
import { getDirname } from './utils/paths.js';

const __dirname = getDirname(import.meta.url);

Note that you can’t just export __dirname from the utility file and import it. The value must be computed using import.meta.url of the specific file that needs it, because import.meta.url refers to the current file, not the file where the function is defined.


Real-World Example: Express Static Files

A common place this error appears is setting up static file serving in Express when you’ve migrated to ES modules:

CommonJS (old):

js
const express = require('express');
const path = require('path');

const app = express();
app.use('/static', express.static(path.join(__dirname, 'public')));

ES Modules (fixed):

js
import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';

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

const app = express();
app.use('/static', express.static(path.join(__dirname, 'public')));

The two extra lines at the top are the only change needed. Everything after that works identically. This kind of migration pattern is becoming more common as Node.js projects modernize their codebases. Understanding how module systems evolve is part of keeping up with the JavaScript ecosystem, which itself is a topic that intersects increasingly with broader software automation trends, as explored in Robot Process Automation Is the Future of Business on DataWider.


Why You Can’t Use process.cwd() Instead

Developers sometimes try process.cwd() as a substitute for __dirname, but it doesn’t work the same way:

js
// Wrong — resolves to the current working directory, NOT the module's directory
const filePath = path.join(process.cwd(), 'config.json');

process.cwd() returns the directory from which Node.js was launched, not the directory where the current file lives. If you run your app from /home/user but the file is at /home/user/src/server.js, process.cwd() gives you /home/user, not /home/user/src. Use import.meta.url to get the correct module-relative path.

Understanding these subtle distinctions in how JavaScript resolves file paths is increasingly important as more educational platforms and tools adopt ES modules. Big Data Analytics in the Education Field on DataWider covers how technology platforms in education are evolving, and many of those platforms run on Node.js-based backends where this exact issue arises.


Key Takeaways

__dirname is not defined in ES module scope happens because __dirname is a CommonJS-only global. ES modules use URL-based resolution instead.

Here’s every fix at a glance:

  • Universal fix (all Node.js versions): const __dirname = dirname(fileURLToPath(import.meta.url))
  • Modern fix (Node.js 20.11+): use import.meta.dirname directly
  • For file loading: use new URL('./file.json', import.meta.url) without defining __dirname
  • Multiple files: create a shared helper that accepts import.meta.url as an argument
  • Don’t want ES modules? Remove "type": "module" from package.json or rename files to .cjs
  • Don’t use process.cwd() as a substitute — it resolves to the launch directory, not the module directory

The two-line boilerplate using fileURLToPath is the most widely compatible fix and is what the Node.js documentation recommends for cross-version projects. The adoption of ES modules in Node.js reflects a broader shift in how software is built for both server and client environments. AI’s Influence on Business Software on DataWider discusses how these technology shifts are reshaping the tools developers build and use.