Skip to content
Node.js nd core 4 min read

The path Module

File paths look deceptively simple until your code has to run on both Linux and Windows. Separators differ (/ vs \), relative segments like .. need normalizing, and string concatenation quietly produces broken paths. Node’s built-in path module handles all of this for you, giving you reliable, platform-aware helpers for building, parsing, and inspecting paths. It is purely a string-manipulation utility — it never touches the filesystem — which makes it fast and safe to use everywhere.

Importing the module

path is a core module, so there is nothing to install. Use the node: prefix to make the import unambiguous and protect against npm packages shadowing it.

import path from 'node:path';

// CommonJS equivalent:
// const path = require('node:path');

Joining path segments

path.join() concatenates segments using the platform’s separator and normalizes the result, collapsing redundant slashes and resolving . and .. segments. It is the right tool for building a path from known pieces.

import path from 'node:path';

const full = path.join('/var', 'www', 'app', 'index.html');
console.log(full);

const messy = path.join('uploads/', '/2026', '..', 'images', 'logo.png');
console.log(messy);

Output:

/var/www/app/index.html
uploads/images/logo.png

Resolving to an absolute path

path.resolve() processes its arguments from right to left, prepending segments until it has built an absolute path. If no absolute segment is supplied, it anchors the result to the current working directory. Reach for resolve when you need a guaranteed absolute path — for example, before passing it to fs.

import path from 'node:path';

console.log(path.resolve('src', 'config.json'));
console.log(path.resolve('/etc', 'app', 'settings.yml'));
console.log(path.resolve('/etc', '/usr', 'local'));

Output:

/home/dev/project/src/config.json
/etc/app/settings.yml
/usr/local

The key difference: join simply glues segments together, while resolve produces an absolute path and lets a later absolute segment discard everything before it. Use join for relative composition, resolve when you need an absolute anchor.

Inspecting parts of a path

These functions extract individual pieces of a path string. They are purely lexical — the file does not need to exist.

FunctionReturnsExample inputResult
path.basename(p)Last portion (file name)/app/logo.pnglogo.png
path.basename(p, ext)File name without extension/app/logo.png, .pnglogo
path.dirname(p)Directory portion/app/logo.png/app
path.extname(p)Extension including the dot/app/logo.png.png
import path from 'node:path';

const file = '/var/www/app/report.final.pdf';

console.log(path.basename(file));            // report.final.pdf
console.log(path.basename(file, '.pdf'));    // report.final
console.log(path.dirname(file));             // /var/www/app
console.log(path.extname(file));             // .pdf

Output:

report.final.pdf
report.final
/var/www/app
.pdf

Note that extname reports only the final extension: for archive.tar.gz it returns .gz, not .tar.gz.

Parsing and formatting

path.parse() returns an object with every component at once, which is handy when you need several pieces. path.format() is its inverse, rebuilding a path string from such an object.

import path from 'node:path';

const parsed = path.parse('/var/www/app/report.pdf');
console.log(parsed);

const rebuilt = path.format({
  dir: '/var/www/app',
  name: 'report',
  ext: '.pdf',
});
console.log(rebuilt);

Output:

{
  root: '/',
  dir: '/var/www/app',
  base: 'report.pdf',
  name: 'report',
  ext: '.pdf'
}
/var/www/app/report.pdf

The platform separator

path.sep is the segment separator for the current platform (/ on POSIX, \ on Windows). It is occasionally useful for splitting a path into its segments, though prefer the dedicated parsing functions where they fit.

import path from 'node:path';

const segments = '/var/www/app'.split(path.sep).filter(Boolean);
console.log(segments);

Output:

[ 'var', 'www', 'app' ]

There is also path.delimiter: on POSIX, ; on Windows — for splitting environment variables like PATH.

path.posix vs path.win32

By default, the path module operates in the style of the platform your program is running on. Sometimes you need to force a specific convention regardless of the host OS — for instance, generating POSIX-style URLs on a Windows build server, or parsing a Windows path on Linux. Two sub-modules give you that control:

  • path.posix always uses forward slashes and POSIX rules.
  • path.win32 always uses backslashes and Windows rules (including drive letters).
import path from 'node:path';

console.log(path.posix.join('assets', 'css', 'site.css'));
console.log(path.win32.join('assets', 'css', 'site.css'));

console.log(path.win32.basename('C:\\Users\\dev\\app.js'));

Output:

assets/css/site.css
assets\css\site.css
app.js

When building paths that end up in URLs or are stored in cross-platform files (like manifests), use path.posix explicitly so a Windows machine doesn’t emit backslashes.

Best Practices

  • Build paths with join or resolve instead of string concatenation — they handle separators and redundant segments correctly.
  • Use path.resolve() whenever a downstream API needs an absolute path, anchoring relative inputs to a known directory.
  • Import core modules with the node: prefix to avoid accidental shadowing by npm packages.
  • For module-relative paths in ES modules, combine import.meta.dirname (Node 20.11+) with path.join rather than hardcoding directories.
  • Use path.posix when generating URL-style paths so the output stays consistent across operating systems.
  • Reach for path.parse() when you need multiple components at once instead of calling basename, dirname, and extname separately.
Last updated June 14, 2026
Was this helpful?