fs.readdir()

Lists the names of the entries inside a directory asynchronously.

Signature
fs.readdir(path[, options], callback)
  • Added in Node.js v0.1.8

Parameters

ParameterTypeDescription
$path<string> | <Buffer> | <URL>The directory to read. Must be a directory; a file path fails with ENOTDIR.
$optionsoptional<string> | <Object>= { encoding: 'utf8', withFileTypes: false, recursive: false }A string is treated as the encoding. As an object: encoding <string> (default 'utf8', use 'buffer' for Buffer names), withFileTypes <boolean> (default false) to get fs.Dirent objects, recursive <boolean> (default false) to include entries from subdirectories, and signal <AbortSignal>.
$callback<Function>Called as (err, files) where files is <string[]>, <Buffer[]> or <fs.Dirent[]>.

Return value

undefinedNothing. The listing arrives as the callback's second argument: an array of names, Buffers, or fs.Dirent objects.

Errors

Reports ENOENT when the directory does not exist, ENOTDIR when the path is a file, and EACCES when the directory cannot be read, all through the callback's err argument.

Changelog

  • PHP v20.1.0, v18.17.0Added the recursive option, which lists entries from subdirectories as paths relative to the directory read.
  • PHP v18.0.0Passing an invalid callback now throws ERR_INVALID_ARG_TYPE instead of ERR_INVALID_CALLBACK.
  • PHP v10.10.0Added the withFileTypes option, returning fs.Dirent objects and saving a stat call per entry.
  • 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.readdir() returns the names of the entries inside a directory — just the names, not full paths, and not the directory's own contents recursively unless you ask. Join each name onto the directory with path.join() before you stat, read or delete it; forgetting that is the single most common bug on this function, and it silently resolves against the process working directory instead.

Order is not guaranteed

The array comes back in whatever order the filesystem hands over. It is often alphabetical on ext4 and often is not on APFS or over a network mount. If your output or your tests depend on order, sort it explicitly.

Two options worth knowing

  • withFileTypes: true gives fs.Dirent objects with isFile() and isDirectory(), saving one stat syscall per entry when you need to separate files from folders.
  • recursive: true descends into subdirectories and returns paths relative to the directory you read, using the platform separator.

. and .. are never included, but dotfiles such as .env are — there is no hidden-file filtering. On a directory with hundreds of thousands of entries the whole listing is materialised in memory at once; fs.opendir() iterates lazily instead.

Example

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

When to use it

  • Find every .json fixture in a folder and load them into a test suite.
  • Build a static site index by listing the pages directory and filtering by extension.
  • Separate files from subdirectories in one pass using withFileTypes before recursing.

Frequently asked questions

Does fs.readdir return full paths?

No, it returns bare entry names. Combine them with the directory yourself using path.join(dir, name); otherwise a later fs.stat() or fs.readFile() resolves the name against the process working directory and usually fails with ENOENT.

Is the order of fs.readdir results alphabetical?

No, the order is whatever the filesystem returns and it varies by platform and filesystem. Call .sort() if you need deterministic output or stable test assertions.

How do I list files in subdirectories too?

Pass { recursive: true }, available since Node 20.1.0 and 18.17.0. On older versions, recurse manually with withFileTypes and check isDirectory() on each entry.

More examples

Telling files from directories with withFileTypes

Each element is an fs.Dirent rather than a string, so the type is available without a separate fs.stat() call per entry. The output labels file.txt as a file and nested as a directory.

Example · javascript
const fs = require('node:fs');

fs.mkdirSync('mixed/nested', { recursive: true });
fs.writeFileSync('mixed/file.txt', 'x');

fs.readdir('mixed', { withFileTypes: true }, (err, entries) => {
  if (err) throw err;

  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
    console.log(entry.name, '->', entry.isDirectory() ? 'directory' : 'file');
  }

  fs.rmSync('mixed', { recursive: true, force: true });
});

Listing a whole tree with recursive

The listing includes nested files as relative paths such as src/utils/date.js, and also the intermediate directories src and src/utils. Entries use the platform separator, hence the normalisation before comparing or printing.

Example · javascript
const fs = require('node:fs');
const path = require('node:path');

fs.mkdirSync('project/src/utils', { recursive: true });
fs.writeFileSync('project/index.js', '');
fs.writeFileSync('project/src/app.js', '');
fs.writeFileSync('project/src/utils/date.js', '');

fs.readdir('project', { recursive: true }, (err, entries) => {
  if (err) throw err;
  const normalised = entries.map((e) => e.split(path.sep).join('/')).sort();
  console.log(normalised);
  fs.rmSync('project', { recursive: true, force: true });
});

When the directory is missing

The failure is ENOENT and entries is undefined, not an empty array. Code that does entries.filter(...) without checking err first will crash with a TypeError that obscures the real cause.

Example · javascript
const fs = require('node:fs');

fs.readdir('no-such-directory', (err, entries) => {
  console.log('error code:', err && err.code);
  console.log('entries:', entries);
});

Discussion

  • Be the first to comment on this lesson.