path.relative()
Computes the relative path that navigates from one location to another.
path.relative(from, to)- Added in Node.js v0.5.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$from | string | The path to navigate away from. |
$to | string | The path to navigate to. |
Return value
stringThe relative path from 'from' to 'to', or a zero-length string if both resolve to the same path.
Errors
Throws a TypeError (ERR_INVALID_ARG_TYPE) if either from or to is not a string.
Changelog
- PHP v6.8.0On Windows, leading slashes for UNC paths are now included in the return value.
path.relative() answers "how do I get from here to there". Both arguments are resolved to absolute paths first — against the current working directory if they are relative — and then the shared prefix is stripped, with one '..' emitted for each remaining level of from.
When to reach for it
Two uses dominate. The first is display and portability: shortening an absolute path against the project root before printing it in a log or writing it into a generated file. The second is containment checking. If path.relative(root, target) starts with '..', then target is outside root; if it is an empty string, the two are the same path. This resolve-then-relative pair is the standard way to reject path traversal, and it is far more reliable than comparing string prefixes, which is fooled by /data-backup sharing a prefix with /data.
What trips people up
Relative inputs are anchored to process.cwd(), not to the calling module, so the result of path.relative('a', 'b') is unaffected by where the file lives but very much affected by where the process started.
The result is a path between two locations. If from is a file rather than its directory, you get one level too many — pass path.dirname(file).
Example
When to use it
- Rejecting user-supplied paths that escape an uploads directory
- Printing short project-relative paths in build output instead of long absolute ones
- Generating an import specifier from one source file to another
Frequently asked questions
How do I check whether a path is inside a directory in Node.js?
Resolve both, then test the relative result: const rel = path.relative(root, path.resolve(root, input)) is inside when rel is '' or when it neither starts with '..' nor is absolute. This beats a startsWith() string check, which wrongly accepts sibling directories that share a name prefix.
Why does path.relative return an empty string?
Because both arguments resolve to the same path, and no navigation is needed to get from one to the other. Treat an empty result as 'the same location' — it is not an error, and if you need a usable path for it, substitute '.' yourself.
Does path.relative use the current working directory?
Yes, whenever an argument is relative. Both arguments are resolved to absolute paths first, using process.cwd() as the anchor, so the same call can return different results depending on where the process was launched. Pass absolute paths when you need a stable answer.
More examples
Using relative() as a containment check
Only the first two inputs stay under the uploads root; the '..' escape and the absolute path are both rejected. Checking the relative result is what makes this correct — a naive target.startsWith(root) test would also accept a sibling directory named 'uploads-old'.
const path = require('node:path');
const root = path.resolve('uploads');
function isInside(userInput) {
const rel = path.relative(root, path.resolve(root, userInput));
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
}
for (const input of ['avatars/me.png', './a/../b.txt', '../../etc/passwd', '/etc/passwd']) {
console.log(input.padEnd(18), 'inside root:', isInside(input));
}Shortening paths for output
The absolute path varies by machine and is noisy in logs; the relative form is stable and short. Build tools use exactly this to print 'src/index.js' rather than a full home-directory path.
const path = require('node:path');
const fs = require('node:fs');
fs.mkdirSync(path.join('rel-demo', 'src'), { recursive: true });
fs.writeFileSync(path.join('rel-demo', 'src', 'index.js'), '');
const root = path.resolve('rel-demo');
const file = path.resolve('rel-demo', 'src', 'index.js');
console.log('absolute is long:', path.isAbsolute(file));
console.log('display as: ', path.relative(root, file));
fs.rmSync('rel-demo', { recursive: true, force: true });
console.log('cleaned up:', fs.existsSync('rel-demo'));From a file, pass its directory
The first call treats Button.js as if it were a directory, producing one '..' too many. Wrapping the source in dirname() gives the specifier you would actually write in an import statement.
const path = require('node:path');
const fromFile = '/srv/app/src/components/Button.js';
const target = '/srv/app/src/utils/date.js';
console.log('wrong: ', path.posix.relative(fromFile, target));
// -> ../../utils/date.js
console.log('correct:', path.posix.relative(path.posix.dirname(fromFile), target));
// -> ../utils/date.js
Discussion