path.extname()
Returns the file extension of a path, from the last dot in the last component.
path.extname(path)- Added in Node.js v0.1.25
Parameters
| Parameter | Type | Description |
|---|---|---|
$path | string | The path to take the extension from. |
Return value
stringThe extension from the last occurrence of '.' to the end of the last portion of the path, or an empty string if there is none.
Errors
Throws a TypeError (ERR_INVALID_ARG_TYPE) if path is not a string.
Changelog
- PHP v6.0.0Passing a non-string as the path argument now throws instead of coercing.
path.extname() returns the substring from the last . in the final path component through the end of the string, including the leading dot. Dots in directory names are ignored, so path.extname('/srv/v1.2/README') is an empty string.
What trips people up
The rule is deliberately narrow, and three cases catch people out:
- Dotfiles have no extension.
path.extname('.gitignore')is'', because a dot at the very start of the component begins the name rather than an extension. Butpath.extname('.env.local')is'.local'— the leading dot is exempt, later ones are not. - Only the last dot counts.
path.extname('archive.tar.gz')is'.gz'. There is no built-in notion of a compound extension; match those yourself. - A trailing dot returns a lone dot.
path.extname('notes.')is'.', which is truthy and one character long, soif (ext)checks treat it as a real extension.
The comparison you write afterwards is your own, and case is preserved verbatim — '.PNG' comes back as '.PNG', so lowercase before comparing against a whitelist of types.
Example
When to use it
- Routing a file to the right parser based on whether it is .json, .yaml or .csv
- Filtering a directory listing down to a set of allowed extensions
- Setting a Content-Type header from an asset's suffix
Frequently asked questions
How do I get a file extension in Node.js?
Call path.extname(filePath), which returns the extension including its leading dot, such as '.json'. It reads only the last dot in the final path component, so 'archive.tar.gz' yields '.gz' and a file with no dot yields an empty string.
Why does path.extname return an empty string for .gitignore?
Because a dot at the start of the filename begins the name rather than an extension, so dotfiles are treated as having no extension. A later dot is still an extension, which is why '.env.local' returns '.local'.
Does path.extname include the dot?
Yes, the returned string always starts with the dot — path.extname('index.html') is '.html', not 'html'. Strip it with .slice(1) if you need the bare suffix, and remember that an empty result means there was no extension at all.
More examples
Filtering a directory listing by extension
Both a.json and b.JSON are matched because the extension is lowercased before comparison — extname() itself preserves case exactly as written. The .gitignore entry is excluded automatically, since its extension is the empty string.
const path = require('node:path');
const fs = require('node:fs');
fs.mkdirSync('extname-demo', { recursive: true });
for (const f of ['a.json', 'b.JSON', 'c.txt', '.gitignore']) {
fs.writeFileSync(path.join('extname-demo', f), '');
}
const json = fs.readdirSync('extname-demo')
.filter((f) => path.extname(f).toLowerCase() === '.json');
console.log('json files:', json.sort());
fs.rmSync('extname-demo', { recursive: true, force: true });
console.log('cleaned up:', fs.existsSync('extname-demo'));Handling compound extensions yourself
extname() returns '.gz' and '.ts' for the first two, which is rarely what you want for tarballs or TypeScript declaration files. Checking an explicit list of compound suffixes first and falling back to extname() covers both shapes.
const path = require('node:path');
const COMPOUND = ['.tar.gz', '.tar.bz2', '.d.ts'];
function fullExtension(file) {
const base = path.basename(file);
const hit = COMPOUND.find((c) => base.toLowerCase().endsWith(c));
return hit ?? path.extname(base);
}
for (const f of ['archive.tar.gz', 'types.d.ts', 'photo.jpeg', '.bashrc']) {
console.log(f.padEnd(16), '->', JSON.stringify(fullExtension(f)));
}Swapping one extension for another
The second line shows extname()'s single-dot rule in action: 'export.tar.gz' becomes 'export.tar.zip', not 'export.zip'. The third shows a dotfile keeping its whole name and gaining a suffix, since its extension was empty.
const path = require('node:path');
function withExtension(file, ext) {
return path.join(path.dirname(file), path.basename(file, path.extname(file)) + ext);
}
console.log(withExtension('src/pages/about.md', '.html'));
console.log(withExtension('data/export.tar.gz', '.zip'));
console.log(withExtension('config/.eslintrc', '.json'));
Discussion