mrkeyoor.com_
Wed 05 Aug 23:13 UTC
npmCLI & Toolingupdated 05 Aug 2026

rimraf

rimraf is rm -rf for Node: it recursively and aggressively deletes files and directories, cross-platform. It predates Node having any recursive delete of its own, which is how it ended up in 157 million weekly downloads of dependency trees. Modern rimraf (v4 rewrite onward) is promise-based, ships a CLI, supports globs and filters, and carries battle-tested Windows strategies: retry with exponential backoff on EBUSY, and a move-then-remove fallback when Windows refuses to delete in place.

Verdict

Still the right tool when you need Windows resilience, globs, filters, or abort signals; those are real gaps in fs.rm. For a plain recursive delete on modern Node, the built-in does it with zero dependencies, and rimraf knows this, since it delegates to fs.rm on POSIX anyway.

API stability3/5The v4 rewrite broke everything at once (no default export, promises, glob opt-in), and v5 and v6 each changed the contract again; it has been calm since, but a decade of old snippets no longer works.
Docs4/5The README documents every option, strategy, and CLI flag, including honest sections on Windows fallback behavior and the major-version breaks; there is no docs site, but one is not needed.
Maintenance4/5Maintained by isaacs (npm's original author) with pushes through May 2026 and v6.1.3 in February 2026; it is mature plumbing, so slow release cadence is a feature.
Ecosystem5/5157M weekly downloads and a place in most build toolchains; every bundler template and clean script convention assumes it exists.

Use it if

  • You need reliable deletes on Windows, where antivirus and file locking make naive unlink loops fail with EBUSY and EPERM; rimraf's retry and move-remove strategies exist exactly for this
  • You want glob deletes from package.json scripts ('rimraf -g dist/**/*.map') that work identically on every contributor's OS
  • You need a filter function or an AbortSignal on a recursive delete, which Node's built-in fs.rm does not offer
  • You are deleting huge trees and want the parallelized async implementation rather than a sequential loop
Skip it if

Setup reality

npm install rimraf and you are done: hybrid ESM/CJS, TypeScript types bundled, no postinstall anything. The friction is historical, not mechanical: v4 removed the default export and callbacks (promise-based named imports now), v4.2 made globbing opt-in where v3 had it on by default, and v6 raised the Node floor to 20 or 22+. So old snippets from a decade of Stack Overflow answers fail against current majors in three different ways. Also remember the README's own warning: it deletes by design, so never pass it untrusted input.

Patterns

Delete a directory treebasic-delete

import { rimraf } from 'rimraf'

await rimraf('./dist')
// or CJS: const { rimraf } = require('rimraf')

Since v4 there is no default export; import the named function. It resolves to a boolean (true when everything was removed) and does not throw if the path is already gone.

Synchronous deletesync-delete

import { rimrafSync } from 'rimraf'

rimrafSync('./coverage')

The README warns sync is typically slower than async here, because recursive deletion parallelizes well. Use sync only in scripts where blocking is fine.

Delete several paths at oncemultiple-paths

import { rimraf } from 'rimraf'

await rimraf(['./dist', './coverage', './.turbo'])

Accepts an array since v4, so one call replaces Promise.all boilerplate. Paths that do not exist are silently fine.

Delete by glob patternglob-delete

import { rimraf } from 'rimraf'

await rimraf('dist/**/*.map', { glob: true })
// CLI equivalent:
// rimraf -g "dist/**/*.map"

Globbing is opt-in since v4.2; without { glob: true } the pattern is treated as a literal path and nothing matches. This silently 'succeeding' is the top v3-to-modern migration trap.

Cross-platform clean scriptnpm-script-clean

{
  "scripts": {
    "clean": "rimraf dist coverage .cache",
    "prebuild": "rimraf dist"
  }
}

The reason rimraf lives in devDependencies everywhere: 'rm -rf' does not exist in cmd.exe, and this works on every contributor's machine.

Keep some files with a filterfilter-entries

import { rimraf } from 'rimraf'

await rimraf('./cache', {
  filter: (path) => !path.endsWith('.gitkeep'),
})

Filtering forces the JS implementation instead of native fs.rm, so it is slower. Parents of a kept file are kept too, since the directory is no longer empty.

Cancel a long delete with AbortSignalabort-signal

import { rimraf } from 'rimraf'

const ac = new AbortController()
setTimeout(() => ac.abort(), 5000)
await rimraf('./huge-node-modules-tree', { signal: ac.signal })

A signal also opts out of the native fs.rm path, because Node's implementation does not support aborting. Useful to time-box cleanup in CI teardown.

Tune Windows EBUSY retrieswindows-retries

import { rimraf } from 'rimraf'

await rimraf('C:/temp/build', {
  maxRetries: 15,
  backoff: 1.5,
  maxBackoff: 1000,
})

Defaults (10 retries, 1.2 backoff, 200ms cap) handle most antivirus locks. When retries exhaust, the Windows strategy falls back to move-then-remove, which can leave temp-named files behind on failure.

Force the native fs.rm implementationforce-native

import { native, nativeSync } from 'rimraf'

await native('./dist')
// or on the CLI: rimraf --impl=native ./dist

Native is the default on POSIX already; forcing it on Windows trades the retry and fallback logic for speed. --impl=native is also incompatible with --verbose and --interactive.

When you do not need rimraf at allbuiltin-instead

import { rm } from 'node:fs/promises'

await rm('./dist', { recursive: true, force: true })

Zero-dependency equivalent for the plain case since Node 14.14; force ignores missing paths like rimraf does. Reach for rimraf only when you need globs, filters, signals, or Windows fallback.

Alternatives

PackageRegistryPick it when
delnpmYou want glob-first deletion in build scripts with safety rails like dryRun and refusing to delete outside the working directory.
fs-extranpmYou already use fs-extra elsewhere: its remove() is the same idea and one less dependency to add.
premovenpmYou want the smallest possible rm -rf with a CLI and no glob support.