Path in Node.js: A Complete Reference for 2026

The path in Node.js is a built-in utility for working with file and directory paths. It ships with every Node.js installation, so there is nothing to install. Just require it and you are ready. If you have ever had code that worked fine on Linux then broke on a Windows machine because of backslash vs forward slash differences, the path module is the fix.

This guide covers every method and property, shows you how they behave on each OS and walks through real patterns like preventing directory traversal attacks and using path in ES modules.

Why you need the path module

File paths look different depending on the operating system. POSIX systems (Linux, macOS) use / as the separator while Windows uses \. The delimiter in environment variables like PATH also differs. POSIX uses : and Windows uses ;.

If you build paths by concatenating strings, you get bugs that only surface on one OS. For example:

// Don't do this — breaks on Windows
const filePath = __dirname + '/config/settings.json';

The path module handles these differences for you automatically. It also resolves . and .. segments, strips duplicate slashes and lets you parse a path string into its parts or rebuild a path from an object.

How to import the path module

Path is a core module, so no npm install is needed.

CommonJS (the default in most Node.js projects):

const path = require('path');

ES modules (when your package.json has "type": "module" or you use .mjs files):

import path from 'path';

// Or import only what you need
import { join, resolve, basename } from 'path';

Both give you access to the same methods and properties.

__dirname and __filename

Before getting into methods, it helps to know two CommonJS globals you will use with path all the time.

__dirname holds the absolute path of the directory containing the current file. __filename holds the absolute path of the current file itself.

console.log(__dirname);
// /home/user/projects/myapp

console.log(__filename);
// /home/user/projects/myapp/app.js

These globals are injected by Node.js when it runs a CommonJS module. They are not available in ES modules. See the ES modules section below for the workaround.

Path properties

The path module has a few properties that reflect OS values.

path.sep

Returns the platform path segment separator.

console.log(path.sep);
// '/' on Linux and macOS
// '\\' on Windows

You can use path.sep to split a path string into its individual segments:

'/home/user/file.txt'.split(path.sep);
// ['', 'home', 'user', 'file.txt']

path.delimiter

Returns the platform delimiter used in environment variables like PATH.

console.log(path.delimiter);
// ':' on Linux and macOS
// ';' on Windows

// Split the PATH variable into individual paths
const allPaths = process.env.PATH.split(path.delimiter);

path.posix and path.win32

These sub-objects give you access to POSIX or Windows path implementations regardless of which OS you are running on. Useful when you need consistent forward slashes in URLs:

path.posix.join('a', 'b', 'c');  // 'a/b/c' always
path.win32.join('a', 'b', 'c');  // 'a\\b\\c' always

Path methods

path.join()

Joins multiple path segments into one normalized path. It uses the correct separator for the current OS and collapses redundant slashes and . segments.

const filePath = path.join('users', 'john', 'documents', 'file.txt');
// 'users/john/documents/file.txt' on POSIX
// 'users\\john\\documents\\file.txt' on Windows

// Handles '..' and '.' automatically
path.join('/home', 'user', '..', 'jane', 'file.txt');
// '/home/jane/file.txt'

// Cleans up duplicate slashes
path.join('users/', '//docs/', 'file.txt');
// 'users/docs/file.txt'

The most common pattern is joining __dirname with a relative path so your code always finds files relative to the current script, no matter where Node.js is launched from:

const configPath = path.join(__dirname, 'config', 'settings.json');
// '/home/user/projects/myapp/config/settings.json'

path.resolve()

Resolves a sequence of path segments into an absolute path. It works from right to left, prepending each segment until it builds an absolute path. If no absolute segment is found, it prepends the current working directory.

// Resolves relative to current working directory
path.resolve('file.txt');
// '/home/user/project/file.txt'

// With multiple segments
path.resolve('src', 'config', 'settings.json');
// '/home/user/project/src/config/settings.json'

// Last absolute path wins
path.resolve('/users', 'john', '/home', 'file.txt');
// '/home/file.txt'

Use path.resolve() when you need a guaranteed absolute path. Use path.join() when you just want to combine segments correctly.

path.join() vs path.resolve(): what is the actual difference

These two trip people up constantly. Here is a direct comparison:

// join: concatenates with OS separator, no CWD involved
path.join('a', 'b', 'c');
// 'a/b/c'

// resolve: produces absolute path by adding CWD if needed
path.resolve('a', 'b', 'c');
// '/home/user/project/a/b/c'

// With an absolute segment
path.join('/a', '/b', 'c');
// '/a/b/c' (joins literally)

path.resolve('/a', '/b', 'c');
// '/b/c' (last absolute segment wins)

In short: join is about combining strings correctly and resolve is about getting an absolute path.

path.basename()

Returns the last part of a path, which is the filename with extension. Pass a second argument to strip the extension.

path.basename('/home/user/documents/report.pdf');
// 'report.pdf'

path.basename('/home/user/documents/report.pdf', '.pdf');
// 'report'

path.basename('/Users/john');
// 'john' (works on directories too)

path.dirname()

Returns the directory part of a path, which is everything except the last segment.

path.dirname('/home/user/documents/report.pdf');
// '/home/user/documents'

path.dirname('/home/user/documents');
// '/home/user'

// Chain calls to go up multiple levels
path.dirname(path.dirname('/home/user/documents/report.pdf'));
// '/home/user'

path.extname()

Returns the file extension including the leading dot.

path.extname('report.pdf');     // '.pdf'
path.extname('app.js');         // '.js'
path.extname('archive.tar.gz'); // '.gz' (only the last extension)
path.extname('.gitignore');     // '' (dotfiles have no extension)
path.extname('README');         // '' (no extension)

One thing to note: path.extname() only returns the last extension. archive.tar.gz gives you .gz, not .tar.gz. See the FAQ for how to handle compound extensions.

path.parse()

Parses a path string into an object with five properties:

  • root: the root of the path (/ or C:\ on Windows)
  • dir: the directory part including root
  • base: the filename and extension together
  • name: the filename without extension
  • ext: the extension
path.parse('/home/user/documents/report.pdf');
// {
//   root: '/',
//   dir: '/home/user/documents',
//   base: 'report.pdf',
//   ext: '.pdf',
//   name: 'report'
// }

// On Windows
path.parse('C:\\Users\\John\\documents\\report.pdf');
// {
//   root: 'C:\\',
//   dir: 'C:\\Users\\John\\documents',
//   base: 'report.pdf',
//   ext: '.pdf',
//   name: 'report'
// }

This is handy when you need to inspect or modify individual parts of a path without writing regex.

path.format()

The reverse of path.parse(). Takes a path object and returns a path string. Two priority rules: if dir is set then root is ignored, and if base is set then name and ext are ignored.

// Using dir + base
path.format({
  dir: '/home/user/documents',
  base: 'report.pdf'
});
// '/home/user/documents/report.pdf'

// Using dir + name + ext
path.format({
  dir: '/home/user/documents',
  name: 'report',
  ext: '.pdf'
});
// '/home/user/documents/report.pdf'

// Using root + base (root used only when dir is absent)
path.format({
  root: '/',
  base: 'report.pdf'
});
// '/report.pdf'

path.normalize()

Resolves . and .. segments and replaces multiple consecutive slashes with a single separator.

path.normalize('/users/john/../jane/./docs//file.txt');
// '/users/jane/docs/file.txt'

path.normalize('/folder1///folder2///folder3');
// '/folder1/folder2/folder3'

A word of caution: path.normalize() is not a security tool. It cleans up path strings but does not prevent directory traversal attacks. Do not use it as a standalone check on user-supplied input. See the security section below for the correct pattern.

path.relative()

Returns the relative path from one location to another.

path.relative('/home/user/project', '/home/user/project/src/index.js');
// 'src/index.js'

path.relative('/home/user/project/src', '/home/user/project/config');
// '../config'

// Same paths return an empty string
path.relative('/home/user', '/home/user');
// ''

Useful when you need to show a path relative to a project root, for example in error messages or build output.

path.isAbsolute()

Returns true if the given path is absolute.

// POSIX
path.isAbsolute('/home/user');     // true
path.isAbsolute('./relative');     // false
path.isAbsolute('../parent');      // false

// Windows
path.isAbsolute('C:\\Users');      // true
path.isAbsolute('relative\\path'); // false

Using path with the fs module

The path module on its own only manipulates strings. It never reads or writes files. That is what the Node.js fs module is for. The two work together constantly.

Here is a practical example: create a file in the same directory as the current script, then log its absolute path to confirm it landed in the right place.

const path = require('path');
const fs = require('fs');

const fileName = 'output.txt';

// Build the full path relative to the current script
const filePath = path.join(__dirname, fileName);

// Write the file
fs.writeFile(filePath, 'Hello from Node.js!', (err) => {
  if (err) throw err;
  console.log('File written to:', filePath);
});

// Confirm what the absolute path looks like
console.log(path.resolve(fileName));
// '/home/user/projects/myapp/output.txt'

Without path.join(__dirname, fileName), the file would land wherever Node.js was launched from, not necessarily where your script lives. This is one of the most common beginner mistakes when reading files in Node.js.

Here is another pattern you will see in real projects. Load a config file at startup:

const path = require('path');
const fs = require('fs');

const configPath = path.join(__dirname, '..', 'config', 'app.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));

And using path to get the list of files in a folder with an extension filter:

const path = require('path');
const fs = require('fs');

const dir = path.join(__dirname, 'uploads');

fs.readdirSync(dir)
  .filter(file => path.extname(file) === '.pdf')
  .forEach(file => {
    console.log(path.join(dir, file));
  });

Path in ES modules

In CommonJS, __dirname and __filename are always available. In ES modules they are not. Node.js does not inject them because ES modules follow browser-compatible standards and browsers do not have filesystem paths. The workaround uses import.meta.url, which contains the URL of the current module file.

import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

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

// Now use them exactly like you would in CommonJS
const configPath = join(__dirname, 'config', 'settings.json');

Node.js 20.11+ also added import.meta.dirname and import.meta.filename as direct equivalents, so the workaround above becomes optional on newer runtimes:

// Node.js 20.11+ only
const configPath = join(import.meta.dirname, 'config', 'settings.json');

If you are deciding between require and import, our comparison of Node require vs ES6 import walks through the tradeoffs in detail.

Preventing directory traversal with path

Path manipulation goes wrong fast when user input is involved. A directory traversal attack happens when someone passes ../../../etc/passwd as a filename and your server reads it.

The fix is to resolve the full path and then verify it still sits inside the allowed directory:

const path = require('path');
const fs = require('fs');

const UPLOAD_DIR = path.resolve(__dirname, 'uploads');

function safeReadFile(userInput) {
  // Resolve the full path
  const resolved = path.resolve(UPLOAD_DIR, userInput);

  // Reject anything that escapes the allowed directory
  if (!resolved.startsWith(UPLOAD_DIR + path.sep)) {
    throw new Error('Access denied: path is outside the uploads directory');
  }

  return fs.readFileSync(resolved, 'utf8');
}

// Safe
safeReadFile('invoice.pdf');
// '/home/app/uploads/invoice.pdf' — OK

// Blocked
safeReadFile('../../../etc/passwd');
// Throws: 'Access denied: path is outside the uploads directory'

path.sep is appended to UPLOAD_DIR in the startsWith check so that a directory named uploads-extra cannot slip past as a prefix match.

If you want to go deeper on module resolution errors that relate to paths, our guide to fixing the Node.js cannot find module error covers the common causes step by step.

Practical patterns

Get the project root

A common need is finding the project root (the folder with package.json) from any file in the project:

const path = require('path');

// Go up one level from the current file's directory
const projectRoot = path.resolve(__dirname, '..');

const distPath = path.join(projectRoot, 'dist');
const publicPath = path.join(projectRoot, 'public');

Build bundler config paths

Path is a standard fixture in bundler configs like webpack:

const path = require('path');

module.exports = {
  entry: path.join(__dirname, 'src', 'index.js'),
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
};

Switch file extensions

const path = require('path');

function changeExtension(filePath, newExt) {
  const { dir, name } = path.parse(filePath);
  return path.format({ dir, name, ext: newExt });
}

changeExtension('/home/user/report.md', '.html');
// '/home/user/report.html'

This pattern is handy in static site generators or when outputting compiled files next to their sources. You can read more about how this kind of file operation works in our tutorial on writing files in Node.js.

Key Takeaways

  • The path module is built into Node.js, no install needed, just require('path').
  • Always use path.join() or path.resolve() instead of string concatenation for file paths.
  • path.join() combines segments with the OS separator while path.resolve() returns an absolute path.
  • __dirname gives the current script’s directory in CommonJS. Use import.meta.url with fileURLToPath in ES modules.
  • path.parse() breaks a path into root, dir, base, name and ext. path.format() goes the other way.
  • path.normalize() cleans up slashes and .. segments but is not a security check on its own.
  • Always validate user-supplied paths with path.resolve() plus a startsWith boundary check to block directory traversal.

Frequently Asked Questions (FAQ)

What does the path module do in Node.js?

It provides utilities for creating, parsing and manipulating file and directory path strings in a cross-platform way without needing to handle OS separators yourself.

Is the path module built into Node.js?

Yes. It is a core module that ships with every Node.js installation. You do not need to run npm install path.

What is the difference between path.join() and path.resolve()?

path.join() concatenates segments with the correct OS separator. path.resolve() produces a guaranteed absolute path, falling back to the current working directory if no absolute segment is found.

How do I use the path module in ES modules?

Import it with import path from 'path' and reconstruct __dirname using fileURLToPath(import.meta.url) combined with path.dirname(). Node 20.11+ provides import.meta.dirname directly.

What is __dirname in Node.js?

It is a CommonJS global that holds the absolute path of the directory containing the current script. It is not available in ES modules.

Can path.normalize() prevent directory traversal attacks?

No. It removes redundant segments but does not prevent traversal. Use path.resolve() and check that the result still starts with your allowed base directory.

Does the path module read or write files?

No. It only manipulates path strings. To read and write files, pair it with the Node.js fs module.

The path module is one of those quiet workhorses you end up reaching for in almost every Node.js project. Learn the handful of methods above and path-related bugs across OS environments become much rarer.

Aneesha S
Aneesha S
Articles: 169