Join our Newsletter — 33% off our NHI Course

How should teams replace __dirname and __filename when migrating Node.js code from CommonJS to ES modules?

Use import.meta.url as the module reference, then convert it to a file path only when you need path semantics. In modern Node.js, import.meta.dirname and import.meta.filename provide the direct equivalents, while older code can reconstruct them with fileURLToPath and path.dirname. The safest approach is to prefer URL-based access where possible, then fall back to path helpers only for APIs that require strings.

Using file URLs first, then paths only when an API needs strings

When a CommonJS module moves to ES modules, the real shift is that location is no longer exposed as a pair of implicit globals. import.meta.url is the canonical module reference, so the safest pattern is to stay in URL form for as long as possible and convert only at the boundary where a Node.js API still expects a filesystem path.

This matters because URLs and paths are not interchangeable in every case. A URL-based approach preserves the module’s native representation and avoids unnecessary conversion bugs, while path conversion is a compatibility step for legacy APIs, directory joins, or code that must interact with libraries still built around string paths.

  • Use import.meta.url when you need the current module’s location.
  • Use fileURLToPath only when an API requires a path string.
  • Use path.dirname() on that converted path if you need the enclosing directory.

Modern Node.js now provides import.meta.dirname and import.meta.filename as direct equivalents, which simplifies the common migration case. The practical rule is to treat them as convenience accessors, not as a reason to abandon URL-native code patterns where URLs already fit the task.

Where CommonJS replacements usually break during migration

The most common mistake is to mechanically recreate __dirname and __filename everywhere, then keep writing ES modules as if they were CommonJS. That works for basic filesystem lookups, but it obscures the bigger change: ES modules are designed to operate cleanly with URL semantics, especially for relative module references and resource loading.

Path conversion becomes necessary when code hands values to older libraries, native addons, or Node.js APIs that still expect paths rather than URLs. The conversion itself is straightforward, but migration issues usually appear in the surrounding logic, such as joining paths too early, assuming platform separators, or mixing URL and path operations in the same expression.

For teams modernising older code, the useful question is not “how do I recreate the globals exactly?” but “which call sites truly need a path string?” That distinction helps keep the migration small and avoids turning every ES module into a compatibility wrapper around the old runtime model. If your code only needs to resolve sibling modules or assets, staying with URLs is often cleaner and less error-prone than converting immediately.

Standards & Framework Alignment

This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.

NIST CSF 2.0 and CIS Controls v8 set the governance and control requirements practitioners need to meet.

Framework Control / Reference Relevance
NIST CSF 2.0 PR.AC — Access Control Module path conversion often supports controlled access to files and resources.
Recommendation — Apply access control discipline when code reads or resolves filesystem resources.
CIS Controls v8 16 — Application Software Security ES module migration is a software change that can introduce path-handling defects.
Recommendation — Review migrated modules for path handling and resource-loading regressions.

Practitioner Guidance

What to verify: Inventory every __dirname and __filename usage and classify each one by purpose. If the value is only being used to resolve another module or asset, keep it in URL form; if it is passed to a path-only API, convert at the final boundary.

Common mistake: Teams often introduce helper variables that recreate the old globals everywhere, then continue to chain path logic through code that could remain URL-native. That increases migration surface area and makes future refactoring harder, especially when third-party APIs accept either form inconsistently.

Decision rule: Prefer import.meta.dirname and import.meta.filename where your Node.js baseline supports them. If you must support older runtimes, use fileURLToPath(import.meta.url) once, then derive only the specific path values you actually need.

Practitioner takeaway: The clean migration target is not a perfect drop-in replacement for CommonJS globals, it is a codebase that keeps module identity in URL form and converts to paths only when the surrounding API truly requires strings.