fs.stat()
Retrieves metadata about a path, such as its size, type and timestamps.
fs.stat(path[, options], callback)- Added in Node.js v0.0.2
Parameters
| Parameter | Type | Description |
|---|---|---|
$path | <string> | <Buffer> | <URL> | The path to inspect. Symlinks are followed, so the stats describe the target; use fs.lstat() to describe the link itself. |
$optionsoptional | <Object>= { bigint: false } | bigint <boolean> (default false), which makes the numeric fields of the returned fs.Stats object BigInt, and throwIfNoEntry <boolean> (default true), added in v25.7.0, controlling whether a missing entry is an error or yields undefined. |
$callback | <Function> | Called as (err, stats) where stats is an <fs.Stats> instance. |
Return value
undefinedNothing. An fs.Stats object arrives as the callback's second argument.
Errors
Reports ENOENT for a missing path and EACCES when a parent directory is not searchable through the callback's err argument; in that case stats is undefined.
Changelog
- PHP v25.7.0Accepts a throwIfNoEntry option controlling whether a missing entry raises an error or yields undefined.
- PHP v18.0.0Passing an invalid callback now throws ERR_INVALID_ARG_TYPE instead of ERR_INVALID_CALLBACK.
- PHP v10.5.0Accepts an options object with bigint, so sizes and inode numbers can be returned as BigInt.
- PHP v10.0.0The callback parameter is no longer optional; omitting it throws a TypeError at runtime.
- PHP v7.6.0path may be a WHATWG URL object using the file: protocol.
fs.stat() answers the questions a path listing cannot: how large is this, is it a file or a directory, when was it last modified, what are its permission bits. The result is an fs.Stats object whose type predicates — isFile(), isDirectory(), isSymbolicLink() and friends — are methods, not properties. Writing stats.isFile without the parentheses gives you a function, which is always truthy; that is a classic silent bug.
stat, lstat and fstat
This one follows symlinks and describes the target, so a link to a file reports isFile() as true and isSymbolicLink() as false. Use fs.lstat() when you care about the link itself, and fs.fstat() when you already hold a file descriptor.
Numbers and timestamps
size is in bytes and is meaningful only for regular files — a directory's size is a filesystem bookkeeping figure, not the total of its contents. Timestamps come in two forms: mtime as a Date and mtimeMs as a number, plus nanosecond fields when bigint: true. Note that atime is frequently unreliable, since most systems mount with relatime or noatime and update it lazily or never.
Example
When to use it
- Reject an upload that exceeds a size limit before processing its contents.
- Decide whether a directory entry should be recursed into or read as a file.
- Skip regenerating a cached artefact when its source has not changed since the last build.
Frequently asked questions
How do I get the size of a file in Node.js?
Call fs.stat() (or fs.statSync()) and read the size property of the returned fs.Stats object, which is the length in bytes. For files larger than 9 petabytes, pass { bigint: true } to keep the value exact.
What is the difference between fs.stat and fs.lstat?
fs.stat() follows symbolic links and describes the file they point to; fs.lstat() describes the link itself. Use lstat when walking a tree so that a symlink loop does not send you round forever.
Why is stats.isFile always true in my code?
You are almost certainly reading it as a property instead of calling it. isFile is a method, so stats.isFile is a function object that is always truthy — write stats.isFile() with the parentheses.
More examples
Distinguishing a directory from a file
The same call describes both kinds of entry, and only the regular file has a size that means anything. Note that the directory's own size field is deliberately not printed — it reflects filesystem bookkeeping, not the bytes of its contents.
const fs = require('node:fs');
fs.mkdirSync('bundle', { recursive: true });
fs.writeFileSync('bundle/app.js', 'x'.repeat(300));
fs.stat('bundle', (err, dirStats) => {
if (err) throw err;
console.log('bundle isDirectory:', dirStats.isDirectory());
fs.stat('bundle/app.js', (err2, fileStats) => {
if (err2) throw err2;
console.log('app.js isFile:', fileStats.isFile());
console.log('app.js size:', fileStats.size);
fs.rmSync('bundle', { recursive: true, force: true });
});
});A missing path is an error, not empty stats
The callback receives ENOENT and stats is undefined. Anything that reads stats.size before checking err throws a TypeError that hides the real reason, which is why the err check comes first.
const fs = require('node:fs');
fs.stat('missing-file.txt', (err, stats) => {
if (err) {
console.log('code:', err.code);
console.log('stats:', stats);
return;
}
console.log(stats.size);
});Exact sizes with bigint
With bigint: true every numeric field is a BigInt, so sizes beyond Number.MAX_SAFE_INTEGER stay exact. The trade-off is visible in the arithmetic: you must divide by 1024n, not 1024, and convert before mixing with ordinary numbers.
import fs from 'node:fs';
import fsp from 'node:fs/promises';
async function main() {
await fsp.writeFile('big.bin', Buffer.alloc(5000));
const stats = await fsp.stat('big.bin', { bigint: true });
console.log('size type:', typeof stats.size);
console.log('size:', stats.size.toString());
console.log('kilobytes:', Number(stats.size / 1024n));
await fsp.unlink('big.bin');
}
main();
Discussion