stats.isDirectory()
Reports whether a Stats object describes a directory rather than a file or other node type.
stats.isDirectory()- Added in Node.js v0.1.10
Return value
booleantrue if the fs.Stats object describes a file system directory, false otherwise.
Errors
Nothing. It only inspects the file-type bits already present on the Stats object, so any error would have come from the fs.stat() or fs.lstat() call that produced it.
Changelog
- PHP v10.0.0fsPromises.stat() was added, giving a promise-based way to obtain the Stats object this method is called on.
stats.isDirectory() is one of a family of predicates on the fs.Stats object returned by fs.stat(), fs.lstat() and fs.fstat(). It reads the file-type bits of the mode already fetched, so the call itself is free — the cost was the stat() syscall that produced the object.
That is the practical point: fetch one Stats object and ask it several questions rather than stat-ing the same path repeatedly. isDirectory(), isFile(), isSymbolicLink(), isSocket(), isFIFO(), isBlockDevice() and isCharacterDevice() are mutually exclusive; exactly one is true.
stat versus lstat
This is where the bugs live. fs.stat() follows symbolic links, so a link pointing at a directory reports isDirectory() === true and isSymbolicLink() === false. fs.lstat() describes the link itself: isDirectory() is false and isSymbolicLink() is true. A recursive walk built on lstat() therefore skips symlinked directories — often exactly what you want, since it also cannot loop forever on a link that points at its own ancestor.
If you are already listing a directory, fs.readdir() with withFileTypes: true returns Dirent objects carrying the same predicates with no extra syscall per entry. Those reflect link-level type, like lstat().
Example
When to use it
- Split a readdir() listing into subdirectories to recurse into and files to process.
- Guard a recursive delete or copy so it takes the right branch per entry.
- Detect that a configured path is a directory when the code expected a single file.
- Skip symlinked directories in a tree walk by using lstat() and checking isSymbolicLink().
Frequently asked questions
How do I check if a path is a directory in Node.js?
Call fs.stat() (or fsPromises.stat()) on the path and check stats.isDirectory() on the returned Stats object. If the path might not exist, handle the ENOENT error from stat() — there is no variant that returns false for a missing path.
Why does isDirectory() return false for my folder?
Almost always because the Stats object came from fs.lstat() and the path is a symbolic link pointing at a directory — lstat describes the link, so isSymbolicLink() is true and isDirectory() is false. Use fs.stat() to follow the link, or check both predicates if you need to tell the two cases apart.
Is there a faster way than calling stat on every entry in a directory?
Yes — pass { withFileTypes: true } to fs.readdir() and you get Dirent objects that already carry isDirectory() and isFile(), avoiding one stat() syscall per entry. Dirent types reflect the entry itself, like lstat, so a symlink reports isSymbolicLink() rather than the type of its target.
More examples
stat follows symlinks, lstat does not
The same path gives opposite answers: stat reports isDirectory true / isSymbolicLink false, lstat reports false / true. This is the single most common surprise with these predicates. A tree walker using stat() will happily descend through a symlink — and hang forever if that link points at an ancestor directory — whereas one using lstat() treats it as a leaf and terminates.
import fsp from 'node:fs/promises';
await fsp.mkdir('real-dir', { recursive: true });
await fsp.symlink('real-dir', 'link-to-dir');
const viaStat = await fsp.stat('link-to-dir'); // follows the link
const viaLstat = await fsp.lstat('link-to-dir'); // describes the link itself
console.log('stat -> isDirectory:', viaStat.isDirectory(), ' isSymbolicLink:', viaStat.isSymbolicLink());
console.log('lstat -> isDirectory:', viaLstat.isDirectory(), ' isSymbolicLink:', viaLstat.isSymbolicLink());
await fsp.unlink('link-to-dir');
await fsp.rm('real-dir', { recursive: true, force: true });Recursive walk counting files
It prints 4, counting only regular files across every depth. The explicit else-if on isFile() rather than a bare else matters: sockets, FIFOs and device nodes are neither directories nor files, and a plain else would silently count them. In production you would replace the per-entry stat() with fsp.readdir(dir, { withFileTypes: true }), which returns the type alongside each name and halves the syscalls.
import fsp from 'node:fs/promises';
import path from 'node:path';
await fsp.mkdir('proj/src/lib', { recursive: true });
await fsp.mkdir('proj/node_modules/dep', { recursive: true });
await fsp.writeFile('proj/src/app.js', '');
await fsp.writeFile('proj/src/lib/math.js', '');
await fsp.writeFile('proj/src/README.md', '');
await fsp.writeFile('proj/node_modules/dep/index.js', '');
async function walk(dir) {
let total = 0;
for (const name of await fsp.readdir(dir)) {
const full = path.join(dir, name);
const stats = await fsp.stat(full);
if (stats.isDirectory()) total += await walk(full);
else if (stats.isFile()) total += 1;
}
return total;
}
console.log('files under proj:', await walk('proj'));
await fsp.rm('proj', { recursive: true, force: true });
Discussion