fs.readdirSync()
Returns the names of the entries inside a directory, blocking until the listing is complete.
fs.readdirSync(path[, options])- Added in Node.js v0.1.21
Parameters
| Parameter | Type | Description |
|---|---|---|
$path | <string> | <Buffer> | <URL> | The directory to read. 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'), withFileTypes <boolean> (default false) to return fs.Dirent objects, and recursive <boolean> (default false) to include entries from subdirectories. |
Return value
string[] | Buffer[] | fs.Dirent[]An array of the entry names in the directory, as strings by default, Buffers when encoding is 'buffer', or fs.Dirent objects when withFileTypes is true.
Errors
Throws ENOENT when the directory does not exist, ENOTDIR when the path is a file, and EACCES when it cannot be read.
Changelog
- PHP v20.1.0, v18.17.0Added the recursive option, listing entries from subdirectories as relative paths.
- PHP v10.10.0Added the withFileTypes option, returning fs.Dirent objects instead of plain names.
- PHP v7.6.0path may be a WHATWG URL object using the file: protocol.
fs.readdirSync() returns the directory listing directly, which makes it the natural choice in build scripts, code generators, CLI tools and test setup — anywhere you want a plain array to filter and map without a callback or an await.
Sort if you print it
The order reflects the filesystem, not the alphabet. ext4 tends to look sorted, APFS and network mounts often do not, and a snapshot test that passes on a developer laptop can fail in CI purely because of this. Call .sort() whenever the order is observable.
Names, not paths
You get 'app.js', not 'src/app.js'. Join with path.join(dir, name) before reading or statting each entry.
Cost
Everything is materialised at once and the event loop is blocked for the whole scan. On a directory of a few hundred entries that is microseconds; on one with a million it is a visible pause, and fs.opendirSync() streaming entries one at a time is the better tool. Adding recursive: true multiplies that cost by the size of the tree — it will happily walk into node_modules if you point it at a project root.
Example
When to use it
- Collect every migration file in a folder and run them in sorted order.
- Filter a fixtures directory down to .json files at the top of a test file.
- Check whether a build output directory is empty before regenerating it.
Frequently asked questions
Why is fs.readdirSync not returning files in alphabetical order?
Because it returns entries in filesystem order, which is not sorted on most platforms. Call .sort() on the result, or .sort((a, b) => a.localeCompare(b)) if you want locale-aware ordering.
How do I get full paths from fs.readdirSync?
Map over the result with path.join: fs.readdirSync(dir).map(name => path.join(dir, name)). The function only ever returns entry names relative to the directory you passed.
Does fs.readdirSync include hidden files?
Yes. Dotfiles such as .env and .gitignore are included; only '.' and '..' are excluded. Filter with name.startsWith('.') if you want to skip them.
More examples
Sizes and types in one pass
The Dirent objects supply the type for free, so only real files need a stat call. Note the path.join() before statting — passing the bare name would look for logo.png in the working directory and throw ENOENT.
const fs = require('node:fs');
const path = require('node:path');
fs.mkdirSync('assets', { recursive: true });
fs.writeFileSync('assets/logo.png', Buffer.alloc(120));
fs.writeFileSync('assets/style.css', 'body{}');
fs.mkdirSync('assets/icons', { recursive: true });
const entries = fs.readdirSync('assets', { withFileTypes: true })
.sort((a, b) => a.name.localeCompare(b.name));
for (const entry of entries) {
const kind = entry.isDirectory() ? 'dir ' : 'file';
const size = entry.isFile() ? fs.statSync(path.join('assets', entry.name)).size : 0;
console.log(kind, entry.name, size);
}
fs.rmSync('assets', { recursive: true, force: true });Finding files by extension across a tree
The recursive listing returns index.md and pages/about.md as relative paths while notes.txt is filtered out. Separators are normalised first so the same filter works on Windows, where the raw entries contain backslashes.
const fs = require('node:fs');
const path = require('node:path');
fs.mkdirSync('site/pages', { recursive: true });
fs.writeFileSync('site/index.md', '# home');
fs.writeFileSync('site/pages/about.md', '# about');
fs.writeFileSync('site/pages/notes.txt', 'ignore me');
const markdown = fs
.readdirSync('site', { recursive: true })
.map((p) => p.split(path.sep).join('/'))
.filter((p) => p.endsWith('.md'))
.sort();
console.log(markdown);
fs.rmSync('site', { recursive: true, force: true });
Discussion