# notify-rs/notify: a cross-platform filesystem watcher for Rust

> notify is a Rust library that wraps inotify, FSEvents, ReadDirectoryChangesW, kqueue and polling behind one event API. It is a building block, not a program, and the v9 line is still at release candidate stage.

**notify-rs/notify** — 🔭 Cross-platform filesystem notification library for Rust. 

- Repository: https://github.com/notify-rs/notify
- Website: https://docs.rs/notify
- Stars: 3,464 · Forks: 293
- Language: Rust
- License: not declared
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/notify-rs-notify

## What notify actually does, and who reaches for it

notify is a library, not an application. It gives a Rust program a way to learn that files changed without polling the disk itself. The README describes it as a "Cross-platform filesystem notification library for Rust", and that is the whole product. You add it as a dependency, create a watcher, hand it a path, and receive events. There is no daemon to run, no config file, no CLI.

The audience is narrow and specific. The README lists projects that use it: alacritty, cargo watch, cobalt, deno, docket, mdBook, rust-analyzer, watchexec, watchfiles, xi-editor and zed. Those are tools that either rebuild on save, reload a preview, or re-index a codebase. If you are writing that class of software, notify is the layer that saves you from writing per-OS event handling. If you are writing a script that needs to run a command when a file changes, you want a finished tool built on top of notify, not notify itself.

The README also warns that the name collides with something else: "Looking for desktop notifications instead? Have a look at notify-rust or alert-after!" That confusion is common enough to be worth stating up front. This crate watches the filesystem. It does not show popups.

## One API over inotify, FSEvents, ReadDirectoryChangesW, kqueue and polling

The mechanism is backend selection at build time. The README's platform list is the clearest statement of how it works: Linux and Android use inotify; FreeBSD uses inotify when built natively on 14.5 or later and kqueue otherwise, with a `freebsd_inotify` feature for cross-builds; macOS uses FSEvents by default or kqueue via features; Windows uses ReadDirectoryChangesW; iOS, NetBSD, OpenBSD and DragonflyBSD use kqueue; and polling is available on all platforms.

That list is the architecture. notify does not implement a filesystem watcher from scratch. It binds to the native facility each OS already provides, normalises the results into its own event type, and exposes a single watcher interface. The value is that your matching code does not change when you move from a Linux CI runner to a developer's Mac.

The cost is that the backends are not equivalent. inotify and ReadDirectoryChangesW deliver events from the kernel; polling walks the tree on a timer and compares. A program that behaves well under inotify on Linux may miss or delay events under the polling backend, and the README does not claim otherwise. If your correctness argument depends on event delivery guarantees, you have to reason about the backend your target platform actually selects, not about notify in the abstract.

The workspace layout mirrors this split. The repository has separate crates under `notify/`, `notify-types/`, `notify-debouncer-mini/`, `notify-debouncer-full/` and `file-id/`. Event types live in notify-types, the raw watcher lives in notify, and debouncing is a layer you opt into rather than something baked into the core.

## Installing notify and watching a directory for the first time

notify is published on crates.io, so installation is a Cargo dependency. The workspace manifest pins the current line at `notify = { version = "9.0.0-rc.5", path = "notify" }`, which tells you the 9.x series is at release candidate stage. The minimum supported Rust version is 1.88, and the workspace sets `rust-version = "1.88"` with `edition = "2024"`. If your toolchain is older than 1.88, you cannot build this line at all.

Add the dependency to your own manifest:

```toml
[dependencies]
notify = "9.0.0-rc.5"
```

The repository ships runnable examples under `examples/`, including `monitor_raw.rs`, `async_monitor.rs`, `debouncer_full.rs`, `debouncer_mini.rs`, `event_filtering.rs`, `pollwatcher_manual.rs`, `pollwatcher_scan.rs` and `watcher_kind.rs`. Those names map directly onto the decisions you will make: raw events versus debounced, native backend versus polling, and which watcher kind to construct. The examples crate is a workspace member, so it builds alongside the library.

A first real use is watching a source directory and printing what changed. The shape is: construct a watcher, create a channel, register the path with a recursive mode, then loop over received events. The exact constructor names and the event enum variants are documented on docs.rs rather than in the README, which links to the API docs for notify, notify-types and both debouncers. Read `examples/monitor_raw.rs` before writing your own loop; it is the shortest path to a working program and it will show you the current method names for the 9.x line, which differ from the v8 API. The repository includes `docs/UPGRADING_V8_TO_V9.md` precisely because those names changed.

## Raw events, mini debouncing and full debouncing are three different products

The most common mistake with notify is treating the raw event stream as if it were one event per logical change. It is not. A single editor save can produce a create, several modifies, a rename and a metadata touch depending on the backend. The repository acknowledges this by shipping two separate debouncer crates rather than one.

notify-debouncer-mini is the smaller of the two. It coalesces events over a time window and is the right choice when you only need to know that something under a path changed, not what changed. notify-debouncer-full is the larger one, and the workspace pins it at `notify-debouncer-full = { version = "0.8.0-rc.2", path = "notify-debouncer-full" }`. The full debouncer exists because rename detection needs more state than a timer: it has to correlate events across paths to decide whether a file moved or was deleted and recreated. That correlation is also why the `file-id/` crate exists in the workspace, since stable file identity across renames is the hard part.

The practical consequence is that you should decide which layer you need before you write matching logic. If you build a rebuild-on-save tool, mini is usually enough. If you build a file sync or index tool that must distinguish a move from a delete-plus-create, you need the full debouncer, and you should expect to spend time on its event model rather than on notify's raw one.

## Where notify is the wrong tool

notify cannot watch a path that does not exist yet, and it cannot tell you about changes that happened while your process was not running. It reports events as they arrive from the OS. There is no journal, no replay, and the README does not document any persistence layer. If you need to reconcile state after downtime, you have to walk the tree yourself on startup and compare against whatever you recorded.

Recursive watching is also not free. On inotify the kernel has a per-user limit on the number of watches, and a large tree consumes them. The README does not discuss limits, so you should not assume notify manages that budget for you. A repository with hundreds of thousands of files is a different engineering problem from one with a few thousand.

Network filesystems are the other boundary. The native backends are local-filesystem facilities. The README lists polling as available on all platforms, which is the fallback when native events are unreliable, but polling trades latency and CPU for coverage. If your files live on a mounted share and you need low-latency events, verify behaviour on that mount before designing around it.

Finally, macOS has a documented caveat that notify inherits rather than solves. The README's link list includes Apple's FSEvents Programmer Guide section on file system event security, which is the reference for the fact that FSEvents is a directory-level notification mechanism with its own delivery characteristics. notify normalises the API surface; it does not make FSEvents behave like inotify.

## How notify differs from fsnotify and Chokidar

The README states the lineage directly: notify was "Inspired by Go's fsnotify and Node.js's Chokidar, born out of need for cargo watch, and general frustration at the non-existence of C/Rust cross-platform notify libraries." That sentence is the comparison.

fsnotify is the Go equivalent, and it occupies the same position in its ecosystem: a thin binding over the platform facilities with a channel of events. If you are choosing between the two, you are really choosing a language for the surrounding tool, not a watcher design. Chokidar is the Node.js option and it takes a different approach. It is a higher-level library that handles the debouncing and normalisation for you by default, which is why Node tools rarely need a separate debouncer package. notify splits that into opt-in crates, so you get a smaller core and a separate dependency when you want coalescing.

There is also a Rust-internal alternative worth naming: hotwatch, which the README links in its reference list. The difference in approach is that hotwatch presents a callback-oriented interface, while notify's examples centre on a channel you receive from and loop over. If your program is already built around a select loop or an async runtime, the channel model composes more directly. The repository includes `examples/async_monitor.rs`, which is the starting point for that style.

## Licence, MSRV policy and the cost of staying current

The licensing is split across crates, and this matters more than it usually does. The README states that notify itself is licensed under CC0-1.0, while notify-types, notify-debouncer-mini, notify-debouncer-full and file-id are each dual-licensed under MIT or Apache-2.0. CC0 is a public domain dedication; MIT and Apache-2.0 are permissive licences with attribution and, in Apache's case, an explicit patent grant. A project that depends on notify and one of the debouncers is therefore taking on two different licence regimes. Whether that is acceptable depends on your own distribution and legal review, which is not something this article can settle.

The maintenance picture is active. The last push to the default branch was on 2026-09-21, and the most recent release listed is notify-9.0.0-rc.5 from 2026-08-30. The repository is not archived. It also carries `renovate.json`, `deny.toml` and `clippy.toml` at the top level, which is the tooling of a project that expects dependency churn and enforces lint and licence policy in CI.

The upgrade cost is the part to weigh. The 9.x line is at release candidate, and the repository ships `docs/UPGRADING_V8_TO_V9.md` alongside the older `docs/UPGRADING_V4_TO_V5.md`, which tells you the project has broken its API across major versions before and documents how. The MSRV policy is explicitly loose: the README says the current MSRV is 1.88, that "MSRV bumps do NOT require a major release and may happen in minor releases", and that support is guaranteed for the current stable Rust release and the previous two. In practice that means a `cargo update` on a minor version can raise your required toolchain. Pin your dependency and your toolchain if your build environment is not frequently refreshed.

## Conclusion

Adopt notify if you are writing a Rust tool that must react to file changes on Linux, macOS and Windows, and you are willing to pin to the 9.0.0-rc.5 line or stay on a stable 8.x. Do not adopt it if you want a ready-made watcher binary or a stable, semver-final API today. Before committing, check which backend your target platform uses, whether you need the full debouncer, and what the CC0-1.0 licence on the notify crate means for your distribution.

## FAQ

### How do I install notify in a Rust project?

Add it as a Cargo dependency on the 9.0.0-rc.5 line, and make sure your toolchain is at least Rust 1.88, which is the documented minimum supported version. The crate is published on crates.io and the README links to docs.rs for the API.

### Does notify work on Windows and macOS as well as Linux?

Yes. The README lists inotify for Linux and Android, FSEvents by default on macOS with kqueue as an option, and ReadDirectoryChangesW on Windows, plus kqueue on iOS, NetBSD, OpenBSD and DragonflyBSD, and polling on all platforms.

### Do I need a separate debouncer crate with notify?

The core crate delivers raw events, and the repository ships notify-debouncer-mini and notify-debouncer-full as separate workspace members for coalescing them. The full debouncer exists for cases where rename detection matters, and the file-id crate supports stable file identity.

### What licence is notify released under?

The README states that notify itself is licensed under CC0-1.0, while notify-types, notify-debouncer-mini, notify-debouncer-full and file-id are each dual-licensed under MIT or Apache-2.0. The two regimes are not the same, so check both against your own distribution requirements.

## Sources

- [Issues](https://github.com/notify-rs/notify/issues)
- [notify-rs/notify on GitHub](https://github.com/notify-rs/notify)
- [Project website](https://docs.rs/notify)
- [README](https://github.com/notify-rs/notify/blob/main/README.md)
- [Releases](https://github.com/notify-rs/notify/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/notify-rs-notify
