rimraf: cross-platform rm -rf for Node, and what its own README warns about
A `rm -rf` util for nodejs
At a glance
- What is it?
- rimraf is a deep deletion module for Node.js that gives you the behaviour of rm -rf without the platform quirks. It is small, it is blunt, and its README spends more words on safety than on features.
- Who is it for?
- Adopt rimraf if you need recursive deletion inside a Node build, test or cleanup script and you control every path you pass to it. Do not adopt it if any part of that path comes from a user, a network request or an untrusted config file; the README rejects security reports built on that premise, and the move-remove strategy can relocate files to an arbitrary --tmp directory.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 138 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem rimraf solves, and who actually needs it
Node has fs.rm and fs.rmdir, so why does a separate deletion module exist? Because recursive deletion on Windows is not the same operation as recursive deletion on POSIX. Files that are still open, read-only attributes, and the fact that unlink and rmdir are not atomic on Windows all produce failures that a naive recursive walk does not handle. rimraf's README describes its Windows implementation as working around exactly those conditions, and notes that on Windows this implementation is used by default because it is faster and more reliable than the native one.
The audience is narrow and specific. Build tooling authors, test harness authors, and anyone writing a script that has to clear a dist folder, a coverage directory or a node_modules tree before a run. If you are deleting a single file, you do not need rimraf. If you are deleting a tree and you only ever run on Linux, the native path is fine and rimraf mostly gets out of the way.
The README opens with a caution block rather than a feature list, and that framing is honest about what the package is: a tool whose intended purpose is, in its own words, the permanent destruction of filesystem entries.
How rimraf picks a deletion strategy at runtime
The package is a hybrid module. The README shows both an import and a require form, and the package.json exports map confirms it: the import condition resolves to dist/esm/index.js and the require condition to dist/commonjs/index.js, each with its own types file. There is no default export as of v5, so you import the named functions.
Exported strategies are rimraf, rimrafSync, native, nativeSync, manual, manualSync, windows and windowsSync. The plain rimraf() entry point chooses an implementation based on the Node.js version and process.platform. The README states that the native implementation, which wraps Node's built-in fs.rm, is used by default on Node.js versions greater than or equal to 14.14.0. The manual variants use the JavaScript implementation appropriate to the OS, and windows is the JavaScript path tuned for Windows.
Two options force you off the native path. Passing a signal prevents use of fs.rm because that implementation does not support abort signals, and passing a filter does the same because fs.rm does not support filtering. That is a real design consequence: the convenience options cost you the built-in code path.
On the Windows side the fallback is a move-then-remove strategy. When exponential backoff for EBUSY fails to resolve a locked file, rimraf moves it aside to a temp folder and removes it there. The tmp option controls where that folder is, and the README requires it to be on the same physical device as the path being deleted. Defaults are os.tmpdir() when it sits on the same drive letter, otherwise ${drive}:\temp if present, otherwise ${drive}:\.
Install rimraf and delete a real directory tree
The README gives one install instruction: npm install rimraf. The package.json engines field requires node 20 or >=22, so an older runtime will not satisfy the constraint. The CLI binary is declared as ./dist/esm/bin.mjs, and v6 added a --version flag.
For a script, import the named functions and await the async form. The return value is a boolean, true when all entries were removed.
import { rimraf, rimrafSync, native, nativeSync } from 'rimraf'The only case where the promise resolves to something other than true, per the README, is when a filter option omitted an entry from the removal. If you prefer the synchronous form, rimrafSync takes the same arguments, but the README warns it will typically be significantly slower than the async form because recursive deletion parallelizes well.
You can pass an array of paths instead of a single string, and you can pass glob patterns once you opt in with the glob option. On the command line the equivalent switch is --glob. If you want to keep the call bounded in time, pass an AbortSignal through the signal option; the README frames this as useful when removing large folder structures.
The filter option, and the one documented way to get a false back
filter is the most interesting option in the API because it is the only documented path to a non-true return value. It receives the path string and either a Stats or Dirent object for that entry. The first path explored is a Stats, the rest are Dirent. Return a truthy value and the entry is removed; return falsy and it is skipped. Async rimraf accepts a filter that returns a Promise resolving to a boolean.
The semantics have a sharp edge worth reading twice. Omitting a directory does not protect its children: they are still removed unless the filter also excludes them. But any parent of a filtered entry survives, because that directory is no longer empty. So a filter that keeps one file deep in a tree leaves a skeleton of empty parent directories behind. If you expected the filter to prune a subtree, it does not.
There is also a quiet trap for sync users. The README points out that a Promise is truthy, so returning a Promise from a sync filter is the same as not filtering anything at all. A filter written for the async path and reused on rimrafSync will silently delete everything rather than selectively skip. That is the kind of failure mode that shows up in a test run rather than at review time.
Using a filter also disables the native fs.rm path, as noted above, so the filtering convenience is paid for in implementation choice.
Where rimraf is the wrong tool
The README's caution block is unusually direct: you must not pass untrusted input to the function or the CLI tool, just as you would not to rm(1) or unlink(2). It states plainly that security reports relying on untrusted input being passed will be rejected. Treat that as a design boundary, not a bug list.
The move-remove strategy widens the blast radius on Windows. If untrusted parties can supply arguments to the rimraf command line tool, they may also set the --tmp=<dir> folder, which means files can be moved to an arbitrary place on disk rather than simply deleted. A deletion tool that can also relocate data is a different threat model from one that only unlinks.
So rimraf is the wrong tool when the path is derived from a request parameter, an uploaded archive's internal paths, an environment variable a user controls, or a config file shipped by a third party. It is also the wrong tool if you need a reversible operation: there is no trash, no undo and no dry-run mode documented in the README. And if you need to delete across filesystems while preserving anything, the Windows tmp constraint (same physical device as the target) makes the fallback unusable in that configuration.
The preserveRoot option exists as a guardrail: unless you explicitly set it to boolean false, recursive removal of the root directory is not allowed. That default is doing real work.
rimraf versus calling rm -rf or fs.rm directly
The obvious alternative is not another npm package. It is the shell command rimraf is named after, or Node's own fs.rm.
Against rm -rf: the shell command is POSIX-shaped. It does not exist in cmd.exe or PowerShell, and a script that shells out to it is not portable. rimraf's whole reason to exist is that it runs the same way on Windows and POSIX from inside Node. The cost is that you are spawning Node to do it, which matters in a shell script that would otherwise be pure bash. The related search phrase rimraf vs rm rf is a fair question, and the answer is portability plus the Windows retry behaviour, not speed.
Against fs.rm: this is the more interesting comparison. rimraf uses fs.rm under the hood when it can, so on modern Node on Linux you are often just calling the built-in through a wrapper. You would pick rimraf over raw fs.rm when you need the Windows move-then-remove fallback, the maxRetries and backoff controls for EBUSY, EMFILE and ENFILE errors, glob support, or a filter. You would skip rimraf when you want the smallest possible dependency surface and none of those features. Note that rimraf depends on glob, so the install is not dependency-free.
On the Windows retry controls specifically, the defaults are documented: maxRetries is 10 for the Windows implementation and 0 for the native one, backoff defaults to 1.2 and must be greater than 1, and maxBackoff defaults to 200 ms, which the README says works out to 14 retries with the final one delayed 33 ms. retryDelay is native-only and defaults to 100 ms with linear backoff.
Maintenance, licensing and the upgrade path you inherit
The repository is not archived and the last push was on 2026-05-15. The package.json pins version 6.1.3, requires node 20 or >=22, and carries the BlueOak-1.0.0 licence, which is the licence identifier in both the repository metadata and the package manifest. BlueOak-1.0.0 is a permissive licence written in plain language; if your organisation maintains an allowlist of licence identifiers, check that this one is on it, since it is less commonly pre-approved than MIT or Apache-2.0. That is an operational note, not legal advice.
The upgrade cost is mostly in the major-version history. v5 removed the default export, so any code doing a default import breaks. v4 changed the function from callback-style to returning a Promise, and made globbing opt-in through the glob option or the --glob CLI flag. v4.3 changed the resolve value to a boolean. v6 raised the Node floor to 20 or >=22 and added --version. If you are on rimraf 3.x, moving to 6.x is three breaking changes stacked on top of each other, and the callback-to-Promise rewrite is the one that touches every call site.
Development tooling is declared in the repository: tshy handles the dual ESM and CommonJS build, tap runs the tests, oxlint and prettier handle linting and formatting, typedoc generates docs, and a benchmark directory exists with an npm run benchmark script. The build runs through a prepare script, so installing from git triggers a build.
Editorial conclusion
Adopt rimraf if you need recursive deletion inside a Node build, test or cleanup script and you control every path you pass to it. Do not adopt it if any part of that path comes from a user, a network request or an untrusted config file; the README rejects security reports built on that premise, and the move-remove strategy can relocate files to an arbitrary --tmp directory. Before you wire it in, check the engines field against your runtime and confirm whether you need the filter or signal options, since either one rules out the native fs.rm path.
Frequently asked questions
What is rimraf used for?
It removes files and directories recursively from Node.js, described in the README as the UNIX rm -rf command in a cross-platform implementation. Its intended purpose is the permanent destruction of filesystem entries, so it is typically used to clear build output, coverage folders or node_modules before a run.
How do I install rimraf?
The README gives npm install rimraf. The package requires node 20 or >=22 according to its engines field, and the CLI binary is declared as ./dist/esm/bin.mjs.
How do I use rimraf in a script?
Import the named functions, since v5 removed the default export, and await the async form. It returns a boolean that is true when all entries were removed, and false only when a filter option omitted something.
What does rimraf dist do?
It recursively removes the directory named dist, including everything under it. You can also pass an array of paths, as the first parameter accepts either a single path or an array.
Can I install rimraf globally?
The README only documents npm install rimraf, and does not describe a global install. The package does declare a bin entry, so a global install would expose the CLI, but the README does not document that workflow.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/isaacs-rimraf)