dev.club — where best developers and top companies connect.

dev.club — where best developers and top companies connect.Invite only

Request invite

Chokidar Weekly downloads

Minimal and efficient cross-platform file watching library

Why?

Raw fs.watch / fs.watchFile are useless: fs.watch reports many changes as an non-transparent rename, can fire twice for one change, and recursive watching differs across platforms. Chokidar normalizes all of that:

Chokidar watches everything under the paths you give it, so scope them (and use ignored / depth) rather than watching more than you need.

Made for Brunch in 2012, it is now used in 30+ million projects and has proven itself in production environments. The current major is v6 (Oct 2026).

Getting started

npm install chokidar
import chokidar from 'chokidar';
// or: import { watch } from 'chokidar';
// or: const chokidar = require('chokidar');

chokidar.watch('src').on('all', (event, path) => console.log(event, path));

A fuller example:

import chokidar from 'chokidar';

const watcher = chokidar.watch('src', {
  // strings are exact paths (not globs), regexes test the whole path, functions get (path, stats?)
  ignored: [
    /(^|\/)\../, // dotfiles
    (path, stats) => stats?.isFile() && !path.endsWith('.js'), // only .js files
  ],
  ignoreInitial: true, // don't emit add/addDir for files that already exist
});

watcher
  .on('add', (path, stats) => console.log('added', path, stats?.size))
  .on('change', (path) => console.log('changed', path))
  .on('unlink', (path) => console.log('removed', path))
  .on('ready', () => console.log('initial scan done'))
  .on('error', (err) => console.error(err));

watcher.add(['lib', 'index.js']); // add more paths later
watcher.unwatch('lib'); // synchronous
console.log(watcher.getWatched()); // { '/abs': ['src'], '/abs/src': ['a.js', 'sub'], ... }
await watcher.close(); // async and terminal: create a new watcher to resume

Recipes:

// Large or chunked writes, and editors that save via temp file + rename
chokidar.watch('uploads', {
  awaitWriteFinish: { stabilityThreshold: 2000, pollInterval: 100 }, // wait for size to settle
  atomic: 100, // unlink + add within 100 ms becomes one change
});

// Network or otherwise unusual filesystems where fs.watch is unreliable
chokidar.watch('/mnt/nfs/data', { backend: 'polling', pollingInterval: 500 });

API

chokidar.watch(paths, [options]) returns an FSWatcher. paths is a string or an array of strings; files are watched, directories are watched recursively. All options with their defaults:

chokidar.watch('dir-or-file', {
  // Filtering
  ignored: undefined, // matcher or array of matchers, see below
  ignoreInitial: false,
  followSymlinks: true,
  cwd: undefined,
  depth: undefined, // unlimited
  // Backend
  backend: 'auto', // 'auto' | 'native' | 'native-recursive' | 'polling'
  pollingInterval: 100, // polling backend only
  pollingBinaryInterval: 300, // polling backend only
  // Event timing
  atomic: true, // false with backend: 'polling'; or a number of ms
  awaitWriteFinish: false, // or true, or { stabilityThreshold: 2000, pollInterval: 100 }
  alwaysStat: false,
  // Errors and lifecycle
  ignorePermissionErrors: false,
  persistent: true,
});

Filtering

Backend

Event timing

Errors and lifecycle

Methods

Events

Event Listener arguments
add, addDir, change (path, stats?); stats when available, always with alwaysStat
unlink, unlinkDir (path)
all (event, path, stats?) for each of the five events above
ready none; the initial scan is complete
error (error)
raw (event, path, details) from the backend; unstable, use with care

Troubleshooting

Changelog

Upgrading

Version 6 requires Node.js 22.22 or newer. An FSWatcher cannot be reopened after .close() starts; construct a new watcher instead. Polling now defaults atomic to false, while an explicit atomic value is preserved. Use pollingInterval and pollingBinaryInterval for polling configuration; the v5 interval and binaryInterval spellings remain as deprecated aliases.

Globs were removed in v4. To replicate them:

// v3
chokidar.watch('**/*.js');
chokidar.watch('./directory/**/*');

// v4+: filter instead
chokidar.watch('.', {
  ignored: (path, stats) => stats?.isFile() && !path.endsWith('.js'), // only watch js files
});
chokidar.watch('./directory');

// or expand the glob yourself
import { glob } from 'node:fs/promises';
const watcher = chokidar.watch(await Array.fromAsync(glob('**/*.js')));

// unwatching
watcher.unwatch('**/*.js'); // v3
watcher.unwatch(await Array.fromAsync(glob('**/*.js'))); // v4+

Contributing

Run npm ci && npm run build && npm test (tests import the built files, so build first). Internals and design requirements are documented in docs/architecture.md; the cross-platform CI setup is in docs/vm-testing.md.

Also

Why was chokidar named this way? What's the meaning behind it?

Chowkidar is a transliteration of a Hindi word meaning 'watchman, gatekeeper', चौकीदार. This ultimately comes from Sanskrit _ चतुष्क_ (crossway, quadrangle, consisting-of-four). This word is also used in other languages like Urdu as (چوکیدار) which is widely used in Pakistan and India.

License

MIT (c) Paul Miller (https://paulmillr.com), see LICENSE file.

Join libs.tech

...and unlock some superpowers

GitHub

We won't share your data with anyone else.