fsPromises.rm()
Removes a file or directory, with recursive and force options mirroring the rm command.
fsPromises.rm(path[, options])- Added in Node.js v14.14.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$path | string | Buffer | URL | File or directory to remove. |
$optionsoptional | Object | Removal options. |
$options.forceoptional | boolean= false | When true, exceptions will be ignored if path does not exist. |
$options.recursiveoptional | boolean= false | If true, perform a recursive directory removal. In recursive mode, operations are retried on failure. |
$options.maxRetriesoptional | integer= 0 | Number of retries after EBUSY, EMFILE, ENFILE, ENOTEMPTY or EPERM. Ignored unless recursive is true. |
$options.retryDelayoptional | integer= 100 | Milliseconds to wait between retries. Ignored unless recursive is true. |
Return value
Promise<undefined>Fulfills with undefined upon success.
Errors
Rejects with ENOENT if the path does not exist and force is false, and with ERR_FS_EISDIR when the path is a directory and recursive is false.
Changelog
- PHP v14.14.0Added as the replacement for fs.rmdir({ recursive: true }), which was deprecated for recursive removal.
fsPromises.rm() deletes a path. It is the modern replacement for the old unlink + rmdir split and for the deprecated rmdir(path, { recursive: true }): one function that handles files and trees.
The two options do different jobs
People conflate them because rm -rf pairs them on the command line, but they are independent. recursive: true means "descend into directories". force: true means "do not complain if the path is not there". Deleting a populated directory needs the first; making a cleanup step idempotent needs the second.
What trips people up
- Calling
rm()on a directory withoutrecursiverejects withERR_FS_EISDIR— not theENOTEMPTYpeople expect fromrmdir. force: trueonly suppresses missing-path errors. Permission failures and busy files still reject.- On Windows, antivirus scanners and open handles cause intermittent
EBUSY/EPERM. That is whatmaxRetriesexists for, and it is ignored unlessrecursiveis also true. - There is no undo and no trash. A wrong path built by string concatenation deletes real work.
Example
When to use it
- Wipe a build or dist directory before a fresh build.
- Clean up a temporary working folder in a finally block.
- Delete a single uploaded file after processing it.
- Make teardown idempotent with force: true so reruns do not fail.
Frequently asked questions
How do I do rm -rf in Node.js?
await fsPromises.rm(path, { recursive: true, force: true }) — recursive deletes directory contents and force ignores the path not existing.
Why does fsPromises.rm throw ERR_FS_EISDIR?
You pointed it at a directory without recursive: true. rm refuses to remove a directory unless you explicitly opt in to descending into it.
Should I use fs.rmdir or fs.rm to delete a folder?
Use fs.rm — recursive removal via fs.rmdir has been deprecated since Node 14.14.0 and emits a warning. fs.rmdir remains only for removing an already-empty directory.
More examples
force is about missing paths, not about directories
Prints force alone: ERR_FS_EISDIR, then no force: ENOENT, then the success line. It shows the two options solving two different problems — the first error is not something force can fix.
import fs from 'node:fs/promises';
await fs.mkdir('stubborn', { recursive: true });
await fs.writeFile('stubborn/keep.txt', 'x');
try {
await fs.rm('stubborn', { force: true });
} catch (err) {
console.log('force alone:', err.code);
}
try {
await fs.rm('missing-file');
} catch (err) {
console.log('no force:', err.code);
}
await fs.rm('stubborn', { recursive: true, force: true });
console.log('both options worked');Idempotent teardown in a finally block
Prints working in scratch-run then teardown is idempotent. force: true is what lets the same cleanup run after a failure that never created the directory, without a second try/catch.
import fs from 'node:fs/promises';
const work = 'scratch-run';
try {
await fs.mkdir(work, { recursive: true });
await fs.writeFile(`${work}/partial.json`, '{"done":false}');
console.log('working in', work);
} finally {
await fs.rm(work, { recursive: true, force: true });
}
// Running the teardown a second time is still fine.
await fs.rm(work, { recursive: true, force: true });
console.log('teardown is idempotent');Retrying a removal that keeps failing
Prints removed: true. maxRetries and retryDelay only take effect with recursive: true; they exist for Windows, where a virus scanner or lingering handle produces transient EBUSY and EPERM errors that succeed on a second attempt.
import fs from 'node:fs/promises';
await fs.mkdir('busy/inner', { recursive: true });
await fs.writeFile('busy/inner/data.bin', 'x'.repeat(1024));
await fs.rm('busy', {
recursive: true,
force: true,
maxRetries: 3,
retryDelay: 50,
});
console.log('removed:', !(await fs.readdir('.')).includes('busy'));
Discussion