List files in a directory with Node.js
Read entry names with readdir, filter Dirent objects by type, or walk incrementally when directory size and symlink behavior matter.
Use fs.readdir() or its promise and synchronous variants to list directory entries. By default, the result includes names of directories and other entries as well as files, and it does not recurse.
Choose an API style
A CommonJS callback example:
const fs = require('node:fs');
fs.readdir('/path/to/dir', (error, names) => {
if (error) {
console.error('Directory read failed:', error.message);
return;
}
names.forEach((name) => console.log(name));
});
In an ES module:
import { readdir } from 'node:fs/promises';
const names = await readdir('/path/to/dir');
console.log(names);
Or, in a small blocking CommonJS script:
const fs = require('node:fs');
const names = fs.readdirSync('/path/to/dir');
console.log(names);
Replace the example directory with an existing path. Relative paths are resolved from the current working directory.
Filter by entry type
With the promise-based readdir imported above:
const entries = await readdir('/path/to/dir', { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile()) console.log('File:', entry.name);
if (entry.isDirectory()) console.log('Directory:', entry.name);
}
const scripts = entries
.filter((entry) => entry.isFile() && entry.name.endsWith('.js'))
.map((entry) => entry.name);
A filename suffix is not proof of type. Dirent type checks describe the directory entry itself, so a symlink is not classified as its target file. Use a deliberate stat() policy if you want to follow links, and handle disappearing or inaccessible targets.
Walk subdirectories
This ES module generator yields regular files and does not follow symlink entries:
import { opendir } from 'node:fs/promises';
import path from 'node:path';
async function* walk(directory) {
const handle = await opendir(directory);
for await (const entry of handle) {
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) yield* walk(fullPath);
else if (entry.isFile()) yield fullPath;
}
}
for await (const file of walk('/path/to/dir')) console.log(file);
The asynchronous directory iterator closes its handle when iteration finishes or exits. Recursion retains handles for ancestor directories, so very deep trees can still require a more bounded traversal strategy. Errors propagate; decide whether your application should stop or report and skip inaccessible branches.
Modern readdir() also supports { recursive: true }, added in Node 18.17.0 and 20.1.0. You do not always need your own recursion. Choose explicit traversal when type filtering, link policy, or incremental output requires more control. A glob implementation is useful for richer matching rules.
FAQ
Is the output sorted? Do not depend on filesystem enumeration order. Sort names or results explicitly when stable order is required.
Why do I get ENOENT? Check the actual base path and whether the entry disappeared. To locate resources relative to a module, use a module-derived path instead of an accidental working-directory dependency.
What about hundreds of thousands of entries? readdir() materializes its result. opendir() supports incremental iteration and has existed since Node 12.12.0, not only Node 20. Avoid collecting all yielded results if the goal is bounded memory.
References
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan