A drop-in replacement / proxy to Node.js path that:
\ with the Unix / in all string params & results.addExt, trimExt, removeExt, changeExt, and defaultExt.normalizeSafe to preserve any meaningful leading ./ or // (UNC paths), and normalizeTrim which additionally trims any trailing /.joinSafe which works like path.join but preserves leading ./ and //.toUnix to simply convert \ to / and consolidate duplicates.Written in TypeScript with dual CJS/ESM output. Zero runtime dependencies.
import and require() out of the box via package.json exports.docs/API.md for complete input/output tables generated from the test suite.const upath = require('upath') works as before. All functions are available directly on the module (no .default needed).join(), resolve(), and joinSafe() params narrowed from any[] to string[]. Add explicit casts if you pass non-string args: join(myVar as string)._makeLong removed — use toNamespacedPath instead (available since Node 8.3).import { normalize, join, toUnix } from 'upath' works in addition to the default import.String objects rejected — new String('foo') no longer accepted; use plain string primitives.See CHANGELOG.md for the full list of changes.
npm install upath
// ESM
import upath from 'upath'
// or import specific functions
import { normalize, joinSafe, addExt } from 'upath'
// CJS
const upath = require('upath')
upath.normalize('c:\\windows\\nodejs\\path') // 'c:/windows/nodejs/path'
upath.join('some/nodejs\\windows', '../path') // 'some/nodejs/path'
upath.toUnix('.//windows\\//unix//mixed////') // './windows/unix/mixed/'
upath.addExt('myfile', '.js') // 'myfile.js'
upath.changeExt('module.coffee', '.js') // 'module.js'
upath.removeExt('file.js', '.js') // 'file'
upath.defaultExt('file', '.js') // 'file.js'
upath.trimExt('file.min.js') // 'file.min'
Normal path doesn't convert paths to a unified format before calculating paths (normalize, join), which leads to problems:
path on Windows yields different results than on Linux / Mac.path can produce inconsistent results./ on Windows works perfectly inside Node.js, so there's no reason to stick to the Windows \ convention.upath solves this by normalizing all backslashes to forward slashes in every function result.
upath proxies all functions and properties from Node.js path (basename, dirname, extname, format, isAbsolute, join, normalize, parse, relative, resolve, toNamespacedPath, matchesGlob), converting any \ in results to /.
Additionally, upath.sep is always '/' and upath.VERSION provides the package version string.
Below is a summary. See docs/API.md for full input/output tables.
upath.toUnix(path)Converts all \ to / and consolidates duplicate slashes, without any normalization.
upath.toUnix('.//windows\\//unix//mixed////') // './windows/unix/mixed/'
upath.normalizeSafe(path)Like path.normalize(), but preserves a leading ./ and leading // (UNC paths). All backslashes are converted to forward slashes.
upath.normalizeSafe('./path/../dep') // './dep' (path.normalize gives 'dep')
upath.normalizeSafe('//server/share/file') // '//server/share/file'
upath.normalizeTrim(path)Like normalizeSafe(), but trims any trailing /.
upath.normalizeTrim('./../dep/') // '../dep'
upath.joinSafe([path1][, path2][, ...])Like path.join(), but preserves a leading ./ and //.
upath.joinSafe('./some/local/unix/', '../path') // './some/local/path'
upath.joinSafe('//server/share/file', '../path') // '//server/share/path'
upath.addExt(filename, [ext])Adds .ext to filename, but only if it doesn't already have the exact extension.
upath.addExt('myfile', '.js') // 'myfile.js'
upath.addExt('myfile.js', '.js') // 'myfile.js' (unchanged)
upath.addExt('myfile.txt', '.js') // 'myfile.txt.js'
upath.trimExt(filename, [ignoreExts], [maxSize=7])Trims a filename's extension. Extensions longer than maxSize chars (including the dot) are not considered valid. Extensions listed in ignoreExts are not trimmed.
upath.trimExt('my/file.min.js') // 'my/file.min'
upath.trimExt('my/file.min', ['min'], 8) // 'my/file.min' (ignored)
upath.removeExt(filename, ext)Removes the specific ext from filename, if present.
upath.removeExt('file.js', '.js') // 'file'
upath.removeExt('file.txt', '.js') // 'file.txt' (unchanged)
upath.changeExt(filename, [ext], [ignoreExts], [maxSize=7])Changes a filename's extension to ext. If it has no valid extension, the new extension is added. Extensions in ignoreExts are not replaced.
upath.changeExt('module.coffee', '.js') // 'module.js'
upath.changeExt('file.min', '.js', ['min'], 8) // 'file.min.js'
upath.defaultExt(filename, [ext], [ignoreExts], [maxSize=7])Adds .ext to filename only if it doesn't already have any valid extension. Extensions in ignoreExts are treated as if absent, so the default extension is added.
upath.defaultExt('file', '.js') // 'file.js'
upath.defaultExt('file.ts', '.js') // 'file.ts' (already has extension)
upath.defaultExt('file.min', '.js', ['min'], 8) // 'file.min.js' (.min ignored)
Contributions are welcome! Please open an issue or pull request on GitHub.
git clone https://github.com/anodynos/upath.git
cd upath
npm install
npm test # 421 tests
npm run test:integration # CJS/ESM integration tests
If upath is useful to you or your company, please consider sponsoring its continued maintenance.
MIT -- Copyright (c) 2014-2026 Angelos Pikoulas