Node ES modules have no __dirname: use module metadata and file URL helpers
By ON Lab · Sources checked November 8, 2026
Node's CommonJS __dirname variable is not available in an ES module. For a local file module on a supported current Node version, use import.meta.dirname, or derive a directory with fileURLToPath(import.meta.url) and path.dirname. Keep a file URL distinct from a filesystem path, and keep the module's location distinct from process.cwd().
A historical reader using experimental ES modules asked for the same directory string that __dirname supplies in CommonJS. We checked current Node documentation and tested an explicit .mjs module on Node 24.22.2. This is a modern Node workflow, not a claim that the older experimental runtime supports every current metadata property.
Confirm the module mode and the version
node --version
Our fixture used .mjs to make the ES module interpretation explicit. A .js file's interpretation can depend on its package configuration and the runtime's documented rules. Switching the entire project back to another module system is a larger change than obtaining one directory path.
Running a direct console.log(__dirname) in the fixture's ES module raised ReferenceError. The failure was about an unavailable CommonJS binding, not about whether the directory existed.
Use current metadata when the runtime supports it
console.log(import.meta.dirname);
console.log(import.meta.filename);
The current ESM documentation lists these properties for local file modules. It records their introduction in Node 20.11.0 and 21.2.0. We verified dirname in Node 24.22.2; check your exact deployment version rather than assuming a historical ESM flag adds newer APIs.
These metadata properties have a file-protocol scope. A module loaded through another protocol is not guaranteed to provide the same filesystem metadata. This guide tested an actual file module, not a data URL or a browser bundle.
Derive the directory from the module URL
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const filename = fileURLToPath(import.meta.url);
const moduleDirectory = dirname(filename);
console.log(moduleDirectory);
fileURLToPath performs the URL-to-filesystem conversion. path.dirname then selects the containing directory. The result in our fixture matched import.meta.dirname. These are module-local variables; the code does not create a new universal CommonJS global in every ES module.
If a dependency still assumes CommonJS globals, adding a local variable in one unrelated file does not rewrite that dependency. Review the dependency's supported module interface separately.
Why URL pathname is an incomplete shortcut
Our module lived in a directory whose name contained both a space and a # character. Reading new URL(import.meta.url).pathname produced an encoded URL path containing %20 and %23. fileURLToPath returned the actual path characters. A pathname string that looks almost correct can still be the wrong argument for a filesystem operation.
Node's URL documentation also handles platform-specific file URL conversion. Do not replace the helper with a hand-written substring removal or an assumption that every path starts with the same file URL prefix. We did not test Windows drive or network paths locally, so this is a documented portability reason, not a Windows reproduction claim.
For a sibling asset, keep the URL if the API accepts it
import { readFileSync } from 'node:fs';
const text = readFileSync(
new URL('./sample.txt', import.meta.url),
'utf8'
);
console.log(text);
The current ESM documentation describes this module-relative loading pattern. We created an original sample.txt beside the module and read its expected contents through this URL form. It avoided converting to a path merely to pass it back to an API that already accepts a file URL.
Use a path conversion when the consumer requires a filesystem string. Use a supported URL interface when that fits the API. A file URL is not a request to download an HTTP resource; our example read only a local sibling file.
Do not substitute the launch directory
We launched the module from its parent fixture directory. process.cwd() reported that launch directory, while both directory methods reported the module's different containing folder. A relative path such as ./sample.txt supplied directly to a cwd-based file operation would use the caller's directory instead of proving it is beside the module.
Changing the process working directory can affect other code, so choose the anchor according to the intended file contract. A module-owned asset and a user-selected working-directory file are different inputs.
Verify the built artifact in the real deployment
- Record the actual Node version and module mode.
- Check the module URL and supported metadata properties.
- Use fileURLToPath when a filesystem path string is required.
- Test a module path containing spaces or other encoded characters.
- Launch from another directory and verify the intended anchor.
- Check the deployed or bundled artifact's file layout before relying on a sibling asset path.
The module at runtime may be a built output in another directory. TypeScript compilation, bundlers and browser targets can change the context and need their own review. The local result does not validate every build pipeline.
Checked November 8, 2026 with Node 24.22.2 on macOS. The ESM error, current dirname property, fileURLToPath-derived directory, encoded space/# path and sibling URL read were verified. Older experimental Node, Windows, non-file modules, browser targets and bundlers were not tested.