mrkeyoor.com_
Wed 23 Sept 00:33 UTC
npmUtilsupdated 22 Sept 2026

walkdir review

walkdir 0.4.1 traverses a Node filesystem tree through an EventEmitter, a promise helper, or a synchronous call. Its events distinguish files, directories, links, empty folders, sockets, FIFOs, and device nodes. A running emitter can ignore a subtree, queue delivery with pause(), or stop reporting through end(); options cover depth, symlink following, result collection, and a replacement fs object. Our package check found one 196 KB CommonJS package with no dependencies or types. It predates async iteration and is best understood as a legacy event walker rather than a glob engine.

Verdict

walkdir 0.4.1 installed as one 1 MB package in 0.7 seconds with 0 audit findings, but our browser build failed and no types were bundled. Keep it for legacy event-driven walkers; choose fdir or readdirp for a maintained async API in new Node code.

We installed it

Lab card: what happened when we installed walkdirScreenshot of walkdir documentation
Install✓ · 0.7s1 package on disk · 1 MB
ImportESM import works · require() works · CommonJS package
Browsern/acould not be bundled for the browser (Node-only code, most likely)
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does walkdir install cleanly?

Yes. In a fresh container with an empty cache, npm install walkdir finished in 0.7s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.

Can walkdir run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does walkdir work with both ESM and CommonJS?

Yes. Both import 'walkdir' and require('walkdir') worked in Node 22 in our run. The package is published as CommonJS.

Does walkdir include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

walkdir or fdir: which should you use?

fdir: Use it for a maintained high-speed crawler with promise, callback, glob, and builder-style configuration. walkdir 0.4.1 installed as one 1 MB package in 0.7 seconds with 0 audit findings, but our browser build failed and no types were bundled.

When should you not use walkdir?

Release activity or a 1.x compatibility promise is required. npm published 0.4.1 in July 2019, and GitHub shows the last push in December 2022.

API stability3/5Callback, emitter, sync, and promise entry points have not moved since 0.4.1 was published in 2019. That makes established integrations predictable, although the package remains below 1.0. Several observable contracts are defined by source rather than the README, including targetdirectory treatment, maxdepth events, promise rejection on a child fail, and completion-dependent ordering.
Docs3/5The repository README returned HTTP 200 and demonstrates callback, emitter, sync, and promise use. It lists node-type and failure events, dynamic ignore, pause, resume, end, and the filesystem options, including an inode warning for Windows and hard links. It omits emitted depth, maxdepth, root-event differences, async ordering, filter-rejection behavior, and the fact that pause affects delivery rather than filesystem work.
Maintenance2/5npm published 0.4.1 on July 18, 2019, and GitHub records the last push on December 8, 2022. The repository is unarchived and its combined issue and pull-request count is 7, but no current release exercises modern Node behavior for users. A Node >=6 engine declaration, callbacks, and EventEmitter controls show the age of the supported contract.
Ecosystem3/5npm counted 4,233,446 downloads for August 18 through 24, 2026, and GitHub reports 129 stars. Older dependency graphs keep its CommonJS event API in circulation. There is no plugin system, bundled typing, ESM build, glob grammar, or framework layer. The custom fs option is useful for compatible filesystems, but callers must supply stat, lstat, readdir, readlink, and synchronous counterparts when needed.

Use it if

  • Existing CommonJS code already consumes its file, directory, link, empty, fail, and end events.
  • Traversal decisions must ignore a directory from inside an event callback or temporarily queue event delivery.
  • A small Node script needs synchronous and asynchronous walking without adding runtime dependencies.
  • A custom filesystem implementation can provide Node-style stat, lstat, readdir, and readlink methods.
Skip it if

Setup reality

We installed walkdir 0.4.1 in 0.7 seconds in a fresh Node 22 Bookworm container. It left 1 package and 1 MB on disk, with 0 known vulnerabilities from npm audit. The package has no direct or peer dependencies, is 196 KB unpacked, carries an MIT license, and declares Node >=6. It is CommonJS without an exports map. require() and ESM import succeeded in our checks, but no TypeScript declarations were present. Our esbuild browser build failed, as expected for Node filesystem code.

walk(path) begins immediately and returns an EventEmitter, so register listeners in the same turn. The starting directory emits targetdirectory instead of normal path and directory events. Failure to read the target emits error; a child stat or read failure emits fail. walk.async turns every fail into promise rejection, even though filesystem operations already in flight may continue. Use the emitter when inaccessible descendants should be reported without rejecting the entire scan.

The default uses lstat and reports symlinks without following them. follow_symlinks can create cycles. Inode tracking attempts to prevent that, but the README warns about inode identity on Windows and with hard links. Pair following with max_depth when termination matters. no_recurse still reads one level. no_return avoids accumulating a large path array or object when events are the real output.

pause() queues events rather than pausing filesystem reads, and end() suppresses later handling without cancelling outstanding fs calls. Async completion order is not deterministic. A filter receives the current directory and an array of entry names, then must return an array or a promise that resolves to one. Rejected filter promises lack a source-level rejection handler, so keep filters synchronous or catch and convert their failures before returning.

Patterns

Receive each discovered path walk-with-callback

const walk = require('walkdir');

walk('./content', function (pathname, stat) {
  console.log(pathname, stat.isFile());
});

The callback receives path events only. walkdir reports the root through targetdirectory, so it is absent from this callback stream.

Listen by filesystem node type listen-by-node-type

const emitter = walk('./content');

emitter.on('file', (pathname, stat, depth) => {
  console.log('file', depth, pathname);
});
emitter.on('directory', (pathname, stat, depth) => {
  console.log('directory', depth, pathname);
});

Traversal begins at once, so listeners belong directly after walk(). Event order follows asynchronous filesystem completion and is not sorted.

Await a list of paths await-path-list

const paths = await walk.async('./content');
console.log(paths);

One child fail rejects walk.async. Sort the resulting array yourself when callers depend on stable order.

Return stats indexed by path await-path-stats

const entries = await walk.async('./content', {
  return_object: true,
});

for (const [pathname, stat] of Object.entries(entries)) {
  console.log(pathname, stat.size);
}

return_object retains an fs.Stats value for every result. Large directory trees can make that object expensive to keep.

Run a blocking directory walk walk-synchronously

const paths = walk.sync('./fixtures');
for (const pathname of paths) {
  console.log(pathname);
}

walk.sync() blocks Node until traversal finishes. Restrict it to small CLI tasks, tests, or controlled startup work.

Process events without collection stream-without-collecting

const emitter = walk('./large-tree', { no_return: true });

emitter.on('file', (pathname) => indexFile(pathname));
emitter.on('end', () => console.log('done'));

no_return prevents an accumulated array or object. The emitter still reports entries as they arrive.

Filter one directory's entries filter-directory-entries

const emitter = walk('./project', {
  filter(directory, files) {
    return files.filter((name) => name !== 'node_modules' && !name.startsWith('.'));
  },
});

Return names from the supplied files array rather than full paths. Version 0.4.1 lacks a clean rejection path for asynchronous filters.

Ignore a matching subtree ignore-directory-dynamically

const path = require('path');

walk('./project', function (pathname, stat) {
  if (stat.isDirectory() && path.basename(pathname) === '.git') {
    this.ignore(pathname);
  }
});

The callback is bound to the emitter. Invoke ignore() as soon as the directory event arrives, before its pending readdir work proceeds.

Stop descent at a depth limit-walk-depth

const emitter = walk('./project', { max_depth: 3 });

emitter.on('maxdepth', (pathname, stat, depth) => {
  console.warn('not descending', depth, pathname);
});

Source behavior emits maxdepth at the boundary. It does not raise error, despite the option text saying the cap emits an error.

Report child permission failures handle-permission-failures

const emitter = walk('/srv/data');

emitter.on('fail', (pathname, err) => {
  console.warn('skipped', pathname, err?.code);
});
emitter.on('error', (err) => {
  console.error('target failed', err);
});

fail identifies unreadable descendants and can be handled while walking continues. error means the requested root itself could not be read.

Follow symlinks with a bound follow-symbolic-links

const emitter = walk('./tree', {
  follow_symlinks: true,
  max_depth: 20,
  track_inodes: true,
});

emitter.on('link', (pathname) => console.log('link', pathname));

Link cycles can recurse indefinitely. Keep inode tracking on and add max_depth for filesystems where inode identity may collide.

End a running traversal cancel-active-walk

const emitter = walk('./tree');
let files = 0;

emitter.on('file', () => {
  if (++files >= 100) emitter.end();
});

end() stops later result handling, while filesystem calls already started still run. The API has no AbortSignal hook.

Alternatives

PackageRegistryPick it when
fdirnpmUse it for a maintained high-speed crawler with promise, callback, glob, and builder-style configuration.
readdirpnpmUse it for a maintained directory stream with filters and for-await consumption.
fast-globnpmUse it when patterns and ignore rules matter more than events for each filesystem node type.
klawnpmUse it for a readable object stream of paths and stats in older CommonJS code.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.