Node.js Global Objects
Every JavaScript runtime exposes a top-level object that holds values reachable from anywhere without being imported. In the browser that object is window; in Node.js it is global. Understanding Node’s global scope—and how it differs from the browser—helps you avoid surprising bugs, write portable code, and know which “magic” variables actually exist in your modules. This page covers global, the cross-platform globalThis, and the CommonJS-only __dirname and __filename.
The global object
In Node.js, global is the object that owns the global namespace. Anything attached to it is visible everywhere in your process without an import or require. Many of the APIs you use daily—console, process, setTimeout, fetch, structuredClone, URL, and Buffer—are properties of global.
console.log(typeof global); // 'object'
console.log(global.console === console); // true
console.log(global.setTimeout === setTimeout); // true
global.appName = "DevCraftly";
console.log(appName); // accessible without the global. prefix
Output:
object
true
true
DevCraftly
Unlike the browser, declaring a top-level var, let, const, or function in a Node module does not attach it to global. Node wraps every module in a function scope, so module-level declarations stay private to that file. This is a deliberate design choice that keeps modules isolated.
Avoid writing to
globalfor sharing state between files. It creates hidden, hard-to-trace coupling. Prefer explicitimport/export—a module that exports a value is far easier to test and reason about than one that mutates a global.
globalThis: the portable global
global only exists in Node. The browser uses window (or self in workers). To write code that runs in both environments, use globalThis, a standardized property added in ES2020 that always points at the global object regardless of runtime.
// Works in Node, browsers, Deno, Bun, and Web Workers
globalThis.config = { env: "production" };
console.log(globalThis.config.env);
// In Node, globalThis and global are the same object
console.log(globalThis === global); // true
Output:
production
true
The following table summarizes the global reference in each environment.
| Environment | Global reference | globalThis available |
|---|---|---|
| Node.js | global | Yes |
| Browser main thread | window | Yes |
| Web Worker | self | Yes |
| Deno / Bun | globalThis | Yes |
Prefer globalThis in any code (such as a shared utility or polyfill) that might run outside Node.
__dirname and __filename
__dirname and __filename are not properties of global—they are per-module variables injected by Node’s CommonJS module wrapper. __filename is the absolute path of the current module file, and __dirname is the directory that contains it. They are invaluable for resolving paths relative to a file rather than to the process’s current working directory (process.cwd()), which can differ depending on where the program was launched.
// CommonJS only (a .cjs file or a package without "type": "module")
const path = require("node:path");
console.log(__filename);
console.log(__dirname);
const configPath = path.join(__dirname, "config", "app.json");
console.log(configPath);
Output:
/home/dev/app/server.js
/home/dev/app
/home/dev/app/config/app.json
Because these are module-scoped, two different files report different values—each sees its own location.
ESM differences: no __dirname or __filename
In ES modules (.mjs files, or any package with "type": "module" in package.json), __dirname and __filename are not defined. Referencing them throws a ReferenceError. ESM instead exposes import.meta.url, the file’s URL, from which you can derive the path and directory.
Modern Node (20.11+ and 22) also adds import.meta.dirname and import.meta.filename, giving you the same convenience directly:
// ES module — modern, simplest approach (Node 20.11+)
console.log(import.meta.filename); // absolute file path
console.log(import.meta.dirname); // absolute directory path
For older Node versions, reconstruct them from import.meta.url:
// ES module — portable fallback
import { fileURLToPath } from "node:url";
import { dirname } from "node:path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log(__filename);
console.log(__dirname);
Output:
/home/dev/app/server.mjs
/home/dev/app
If you migrate a CommonJS file to ESM and hit
__dirname is not defined, that is expected. Switch toimport.meta.dirname(or thefileURLToPathpattern above) rather than reaching for a workaround.
Why the browser has no equivalent
Browsers run untrusted code from many origins, so they intentionally do not expose the filesystem. There is no concept of a “current file path” on disk, which is why window has no __dirname or __filename. Node, running trusted server-side code, embraces the filesystem—so these path globals are a Node-specific feature. This is one of the clearest examples of how Node’s globals reflect its server environment rather than mirroring the browser.
Best Practices
- Reach for
globalThisinstead ofglobalwhenever code might run outside Node, so it stays portable. - Don’t pollute the global object to share state; use explicit
import/exportfor clear, testable dependencies. - Use
__dirname(orimport.meta.dirname) withpath.join/path.resolveto build file paths—never hardcode separators or rely onprocess.cwd(). - In ES modules, prefer
import.meta.dirnameandimport.meta.filenameon modern Node, and keep thefileURLToPath(import.meta.url)fallback for older runtimes. - Remember that top-level
const/let/varin a Node module are module-scoped, not global—reading them onglobalreturnsundefined. - Import core modules with the
node:prefix (e.g.,node:path,node:url) to make intent explicit and avoid name collisions.