fsPromises.stat()

Retrieves metadata for a path: size, timestamps, permissions and whether it is a file or directory.

Signature
fsPromises.stat(path[, options])
  • Added in Node.js v10.0.0

Parameters

ParameterTypeDescription
$pathstring | Buffer | URLPath to inspect. Symbolic links are followed; use lstat() to inspect the link itself.
$optionsoptionalObjectStat options.
$options.bigintoptionalboolean= falseWhether the numeric values in the returned fs.Stats object should be bigint.
$options.signaloptionalAbortSignal= undefinedAn AbortSignal to cancel the operation.

Return value

Promise<fs.Stats>Fulfills with an fs.Stats object for the given path.

Errors

Rejects with ENOENT if the path does not exist, ELOOP on a symlink cycle, and EACCES when a parent directory is not searchable.

Changelog

  • PHP v26.1.0Now accepts an additional signal property to allow aborting the operation.
  • PHP v10.5.0Accepts an options object to request bigint numeric values, which is the only way to get nanosecond-precision timestamps.

fsPromises.stat() returns an fs.Stats object describing a path: size in bytes, mode, mtime/atime/ctime/birthtime as Date objects, plus the ms and Ns variants, and predicates such as isFile(), isDirectory() and isSymbolicLink().

When to reach for it

Use it when you need a fact about a file rather than its contents: is this big enough to reject, is it newer than the cached build output, is this entry a directory. Cache invalidation by mtimeMs is the classic case.

What trips people up

  • It follows symlinks. Stat a link and you get the target's metadata, and isSymbolicLink() is always false. Use lstat() to see the link.
  • Do not use it as an existence check before opening a file — that is a TOCTOU race and an extra syscall. Just open it and handle ENOENT.
  • Timestamp resolution is filesystem-dependent; two writes in the same millisecond can share an mtime. Pass bigint: true when you need mtimeNs.
  • size on a directory is the directory entry's own size, not the total of its contents.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Reject an upload that exceeds a size limit before reading it.
  • Skip rebuilding an output file whose mtime is newer than its source.
  • Distinguish files from directories while walking a tree.
  • Report total bytes used by the files in a folder.

Frequently asked questions

How do I get a file's size in Node.js?

const { size } = await fsPromises.stat(path) — size is the length in bytes as a number, or a BigInt if you pass { bigint: true }.

How do I check whether a path is a directory?

(await fsPromises.stat(path)).isDirectory(). If you are already listing a folder, fsPromises.readdir with withFileTypes: true gives you the same answer without a second syscall.

Should I use stat to check if a file exists?

No. It costs an extra syscall and leaves a race window between the check and the open. Attempt the operation and handle ENOENT, or use fsPromises.access() when a check really is all you need.

More examples

Skip work when the output is newer than the source

Prints stale before edit: false, then stale after edit: true. mtimeMs is a plain number, which is why it is easier to compare than the mtime Date object.

Example · javascript
import fs from 'node:fs/promises';

await fs.writeFile('page.md', '# hello');
await fs.writeFile('page.html', '<h1>hello</h1>');

async function isStale(src, out) {
  const [a, b] = await Promise.all([fs.stat(src), fs.stat(out)]);
  return a.mtimeMs > b.mtimeMs;
}

console.log('stale before edit:', await isStale('page.md', 'page.html'));

await fs.writeFile('page.md', '# hello again');
console.log('stale after edit:', await isStale('page.md', 'page.html'));

await fs.rm('page.md');
await fs.rm('page.html');

Total the size of a directory's files

Prints the file list and total bytes: 350. Note that the directory's own stat().size would report the directory entry, not 350 — sizes only add up if you stat the files.

Example · javascript
import fs from 'node:fs/promises';
import path from 'node:path';

await fs.mkdir('assets', { recursive: true });
await fs.writeFile('assets/a.txt', 'x'.repeat(100));
await fs.writeFile('assets/b.txt', 'y'.repeat(250));

const names = await fs.readdir('assets');
const sizes = await Promise.all(
  names.map(async (n) => (await fs.stat(path.join('assets', n))).size)
);

console.log('files:', names.sort());
console.log('total bytes:', sizes.reduce((a, b) => a + b, 0));

await fs.rm('assets', { recursive: true });

stat versus lstat on a symbolic link

stat reports true false 12 — the target's facts — while lstat reports false true. If you are building a directory walker, using stat here is how you end up in an infinite loop on a circular link.

Example · javascript
import fs from 'node:fs/promises';

await fs.writeFile('real.txt', 'twelve chars');
await fs.symlink('real.txt', 'link.txt');

const viaStat = await fs.stat('link.txt');
const viaLstat = await fs.lstat('link.txt');

console.log('stat  ->', viaStat.isFile(), viaStat.isSymbolicLink(), viaStat.size);
console.log('lstat ->', viaLstat.isFile(), viaLstat.isSymbolicLink());

await fs.rm('link.txt');
await fs.rm('real.txt');

Discussion

  • Be the first to comment on this lesson.