NodeJSThe Module Wrapper Function

The Module Wrapper Function

Here is a question that puzzles most Node beginners: where do require, module, exports, __dirname, and __filename come from? You never declared them, yet they exist in every CommonJS file — and they are not globals. The answer is the module wrapper: before running your code, Node wraps it in a function whose parameters are exactly those five names.

What Node actually executes

Your file is never run as-is. Node reads the source and wraps it like this before handing it to V8:

The wrapper Node adds around every CJS module

JS
(function (exports, require, module, __filename, __dirname) {
  // ─── your module's code goes here ───
});

Then Node calls that function, passing in the right values for each parameter. So those five names are ordinary function arguments — which immediately explains their behaviour.

The five injected values

Name

What it is

exports

Shorthand reference to module.exports

require

This module's import function (carries .resolve, .cache, .main)

module

The module object describing this file

__filename

Absolute path of this file

__dirname

Absolute path of the directory containing this file

Why this explains everything
  • Why each file has its own scope — top-level const/let are locals of the wrapper function, so they never leak between files or onto global.

  • Why __dirname differs per file — it is an argument Node computes fresh for each module from its own path.

  • Why exports and module.exports start equal — Node passes module.exports in as the exports argument (see module.exports & exports).

  • Why require knows where you are — it is a per-module function bound to this file, so relative paths resolve against it.

These are NOT on globalThis
`global.__dirname` is `undefined` — proof these are wrapper parameters, not globals. Confusing the two leads people to expect `__dirname` to work in the REPL or in ES modules, where the wrapper does not apply.
Prove it to yourself

Node even exposes the wrapper template. You can print the array of parameter names and the surrounding strings:

JS
const Module = require('node:module')
console.log(Module.wrapper)
[
  '(function (exports, require, module, __filename, __dirname) { ',
  '\n});'
]
ES Modules use a different mechanism
No wrapper in ESM — and no __dirname
ES Modules are not wrapped in this function, so `require`, `module`, `exports`, `__dirname`, and `__filename` do **not** exist there. ESM gets module metadata through `import.meta` instead. Reconstruct the paths when you need them:

The ESM equivalents

JS
import { fileURLToPath } from 'node:url'
import { dirname } from 'node:path'

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

// Node 20.11+ also offers these directly:
console.log(import.meta.dirname, import.meta.filename)
A practical consequence: resolve files relative to the module

__dirname matters because the current working directory (process.cwd()) is wherever the user ran node from — not where your file lives. Always build paths to bundled assets from __dirname, never from a bare relative string:

JS
const path = require('node:path')

// FRAGILE — breaks if launched from another directory
const bad = './config/default.json'

// ROBUST — always relative to THIS file, regardless of cwd
const good = path.join(__dirname, 'config', 'default.json')
Next
Move to the modern standard syntax in [ES Modules (import / export)](/nodejs/es-modules).