fsPromises.readdir()

Lists the entries of a directory, as names or as Dirent objects, optionally recursing into subdirectories.

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

Parameters

ParameterTypeDescription
$pathstring | Buffer | URLDirectory to list.
$optionsoptionalObject | stringOptions object, or a string that is taken as the encoding.
$options.encodingoptionalstring | null= 'utf8'Encoding for the returned names; 'buffer' yields Buffers.
$options.withFileTypesoptionalboolean= falseReturn fs.Dirent objects instead of plain name strings.
$options.recursiveoptionalboolean= falseAlso list the contents of subdirectories, returning paths relative to path.

Return value

Promise<string[] | Buffer[] | fs.Dirent[]>Fulfills with an array of the names of the files in the directory, excluding '.' and '..'. If options.withFileTypes is true, the array contains fs.Dirent objects instead.

Errors

Rejects with ENOENT if the directory does not exist, ENOTDIR if the path is a file, and EACCES if the directory is not readable.

Changelog

  • PHP v18.17.0, v16.17.0The recursive option is now supported; on older releases it is silently ignored, so you get a shallow listing with no error.
  • PHP v10.10.0New optional options argument.

fsPromises.readdir() returns what is inside a directory. By default that is an array of bare names — no leading path, no type information, and '.' and '..' already filtered out.

withFileTypes changes the shape

With withFileTypes: true you get fs.Dirent objects instead, each with name, parentPath, and predicates like isFile(), isDirectory() and isSymbolicLink(). This is almost always what you want when you need to separate files from folders, because the type comes back from the same directory read — no extra stat() call per entry.

What trips people up

  • The results are names, not paths. Feeding them straight into readFile() works only when the directory is your cwd; otherwise join them onto the directory first.
  • Order is filesystem order, not alphabetical. Sort it yourself if output stability matters.
  • recursive: true returns relative paths like logs/app.log, and it walks the whole tree eagerly before resolving — on a big tree, prefer fsPromises.glob(), which streams.
  • Hidden dotfiles are included.

Example

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

When to use it

  • List the files in an uploads folder to process each one.
  • Split a directory into files and subdirectories in a single pass with withFileTypes.
  • Walk a project tree with recursive: true to collect every source file.
  • Check whether a directory is empty before removing it.

Frequently asked questions

How do I get the full path from fsPromises.readdir?

Join the returned name onto the directory yourself with path.join(dir, name) — readdir gives you names only. With withFileTypes: true, each Dirent also exposes parentPath.

What does withFileTypes: true do in readdir?

It makes readdir resolve to fs.Dirent objects instead of strings, each with a name plus isFile(), isDirectory() and isSymbolicLink(). The type comes from the directory read itself, so you avoid a stat() call per entry.

Is the result of readdir sorted?

No — entries come back in whatever order the filesystem returns them, which differs between ext4, APFS and NTFS. Call .sort() if you need deterministic output.

More examples

Join names onto the directory before using them

The first read prints bare name: ENOENT because 'one.txt' is not a path relative to the process cwd. path.join() with the directory fixes it — the mistake behind most readdir ENOENT reports.

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

await fs.mkdir('inbox', { recursive: true });
await fs.writeFile('inbox/one.txt', 'first');
await fs.writeFile('inbox/two.txt', 'second');

const names = await fs.readdir('inbox');

try {
  await fs.readFile(names[0], 'utf8');
} catch (err) {
  console.log('bare name:', err.code);
}

for (const name of names.sort()) {
  console.log(name, '->', await fs.readFile(path.join('inbox', name), 'utf8'));
}

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

Split files from directories without extra stat calls

Prints dirs: [ 'src', 'test' ] and files: [ 'README.md', 'package.json' ]. Each Dirent already knows its type, so this classifies a 10,000-entry directory with one syscall rather than 10,001.

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

await fs.mkdir('project/src', { recursive: true });
await fs.mkdir('project/test', { recursive: true });
await fs.writeFile('project/package.json', '{}');
await fs.writeFile('project/README.md', '# hi');

const entries = await fs.readdir('project', { withFileTypes: true });
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
const files = entries.filter((e) => e.isFile()).map((e) => e.name).sort();

console.log('dirs:', dirs);
console.log('files:', files);

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

Recursive listing versus a shallow one

The shallow call returns [ 'a', 'root.txt' ]; the recursive one returns every descendant as a path relative to 'tree', including the intermediate directories themselves — filter with withFileTypes if you only want files.

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

await fs.mkdir('tree/a/b', { recursive: true });
await fs.writeFile('tree/root.txt', '');
await fs.writeFile('tree/a/mid.txt', '');
await fs.writeFile('tree/a/b/leaf.txt', '');

console.log('shallow:', (await fs.readdir('tree')).sort());
console.log('recursive:', (await fs.readdir('tree', { recursive: true })).sort());

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

Discussion

  • Be the first to comment on this lesson.