url.fileURLToPath()
Converts a file: URL into a correctly decoded, platform-specific absolute filesystem path.
url.fileURLToPath(url[, options])- Added in Node.js v10.12.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$url | URL | string | The file URL string or URL object to convert to a path. |
$optionsoptional | Object | Options controlling how the path is produced. |
$options.windowsoptional | boolean | undefined= undefined | true if the path should be return as a windows filepath, false for posix, and undefined for the system default. |
Return value
stringThe fully-resolved platform-specific Node.js file path.
Errors
Throws TypeError [ERR_INVALID_URL_SCHEME] when the URL's protocol is not file:, and ERR_INVALID_FILE_URL_PATH or ERR_INVALID_FILE_URL_HOST for a file: URL that cannot map to a path.
Changelog
- PHP v22.1.0, v20.13.0The options argument can now be used to determine how to parse the path argument, including forcing Windows or POSIX rules.
fileURLToPath() is the correct translation from a file: URL to a filesystem path. It percent-decodes the path, converts slashes for the host platform, handles Windows drive letters and UNC hosts, and rejects anything that is not a file: URL.
When to reach for it
Almost always in ESM, where __dirname and __filename do not exist. import.meta.url is a file: URL string, and fileURLToPath(import.meta.url) is the supported way to turn it into a path you can hand to fs or path.dirname(). It is also what you need for the url passed to loader hooks and for any API that hands you a module URL.
What trips people up
The hand-rolled alternatives are all wrong. url.replace('file://', '') leaves %20 in place, so any path containing a space breaks. On Windows it also leaves a leading slash before the drive letter, producing /C:/…. And new URL(import.meta.url).pathname has both problems.
A file: URL with a query or fragment silently loses it: fileURLToPath('file:///a?b') returns /a. That matters if you built the URL by string concatenation from a filename containing ? or # — use pathToFileURL() to build it instead.
Example
When to use it
- Get the directory of the current ESM module in place of __dirname
- Resolve a data file that sits next to a module using import.meta.url
- Convert the url argument in a module loader hook to a path for fs
- Normalize file: URLs coming from a tool's config into real paths
Frequently asked questions
How do I get __dirname in an ES module?
Use path.dirname(fileURLToPath(import.meta.url)). Node also exposes import.meta.dirname directly in newer releases, but the fileURLToPath form works across every version that supports ESM.
Why can't I just strip 'file://' from import.meta.url?
Because the URL is percent-encoded: any path with a space, #, or non-ASCII character comes out wrong, and on Windows you are left with a leading slash before the drive letter. fileURLToPath handles decoding and platform rules for you.
What happens if I pass an http URL to fileURLToPath?
It throws a TypeError with code ERR_INVALID_URL_SCHEME. Check url.protocol === 'file:' first if the input might come from somewhere other than a module URL.
More examples
__dirname and __filename in ESM
Simulates the standard ESM shim with a fixed module URL so the output is stable. In a real module you pass import.meta.url; the resulting __dirname behaves exactly like the CommonJS one.
// In a .mjs file or a "type": "module" package:
// import { fileURLToPath } from 'node:url';
// import path from 'node:path';
// const __filename = fileURLToPath(import.meta.url);
// const __dirname = path.dirname(__filename);
const { fileURLToPath } = require('node:url');
const path = require('node:path');
const moduleUrl = 'file:///srv/app/src/lib/loader.mjs';
const filename = fileURLToPath(moduleUrl);
const dirname = path.dirname(filename);
console.log(filename);
console.log(dirname);
console.log(path.join(dirname, '..', 'config.json'));Why manual string slicing fails
Both shortcuts return paths still containing %20 and %23, which fs will look up literally and fail to find. The Windows pair shows the extra defect: the naive form keeps a leading slash before C:, which is not a valid path.
const { fileURLToPath } = require('node:url');
const u = 'file:///home/carol/my%20docs/report%23final.txt';
console.log('correct: ', fileURLToPath(u));
console.log('replace: ', u.replace('file://', ''));
console.log('pathname:', new URL(u).pathname);
const win = 'file:///C:/Users/carol/a%20b.txt';
console.log('windows correct:', fileURLToPath(win, { windows: true }));
console.log('windows replace:', win.replace('file://', ''));Resolving a sibling file from a module URL
new URL(relative, moduleUrl) does the resolution, then fileURLToPath converts once at the boundary where fs is called. In ESM you would write new URL('settings.json', import.meta.url) — the same pattern, and it survives the module being moved.
const { fileURLToPath } = require('node:url');
const fs = require('node:fs');
fs.writeFileSync('settings.json', JSON.stringify({ mode: 'fast' }));
// Given a module URL, resolve a file next to it without touching strings.
const moduleUrl = new URL('file://' + process.cwd() + '/index.js');
const dataUrl = new URL('settings.json', moduleUrl);
console.log(fileURLToPath(dataUrl).endsWith('/settings.json'));
console.log(fs.readFileSync(fileURLToPath(dataUrl), 'utf8'));
fs.unlinkSync('settings.json');
Discussion