fs.unlink()
Deletes a file or symbolic link asynchronously, failing on directories.
fs.unlink(path, callback)- Added in Node.js v0.0.2
Parameters
| Parameter | Type | Description |
|---|---|---|
$path | <string> | <Buffer> | <URL> | The file or symbolic link to remove. A symlink is removed itself, never its target. Directories are not accepted. |
$callback | <Function> | Called as (err), null on success. |
Return value
undefinedNothing. Success or failure is reported through the callback's err argument.
Errors
Reports ENOENT when the file does not exist, EACCES or EPERM on permission failures, and an error on directories (EISDIR on Linux, EPERM on macOS), all through the callback's err argument.
Changelog
- PHP v18.0.0Passing an invalid callback now throws ERR_INVALID_ARG_TYPE instead of ERR_INVALID_CALLBACK.
- PHP v10.0.0The callback parameter is no longer optional; omitting it throws a TypeError at runtime.
- PHP v7.6.0path may be a WHATWG URL object using the file: protocol.
fs.unlink() removes a name from the filesystem. That wording matters: it deletes the directory entry, and the file's data is reclaimed only once the last name pointing to it is gone and no process still holds it open. A file you unlink while a stream is reading it stays fully readable until that stream closes — the disk space simply is not freed yet.
Files and links only
Passing a directory fails (EISDIR on Linux, EPERM on macOS). Use fs.rm() or fs.rmdir() for those. Given a symlink, unlink deletes the link and leaves the target untouched, which is what you almost always want.
Deleting something that is not there
That is ENOENT, not a silent success. Cleanup code that runs twice, or runs after a failed setup, needs to tolerate it — either ignore err.code === 'ENOENT' or use fs.rm(path, { force: true }), which suppresses it for you.
Also note that permission to delete comes from the write bit on the containing directory, not on the file. A read-only file in a writable directory deletes fine; a writable file in a read-only directory does not.
Example
When to use it
- Remove a temporary upload after it has been processed and stored elsewhere.
- Delete a stale lock file or PID file during shutdown.
- Prune expired entries from a cache directory one file at a time.
Frequently asked questions
How do I delete a file in Node.js?
Call fs.unlink(path, callback), or await fs.promises.unlink(path). For a path that might be a directory, or when a missing file should not be an error, use fs.rm() instead.
Why does fs.unlink fail on a directory?
Because unlink removes only files and symbolic links; directories need fs.rm() with recursive: true, or fs.rmdir() when they are already empty. The error is EISDIR on Linux and EPERM on macOS.
Does fs.unlink free the disk space immediately?
Only if no process still has the file open. Unlink removes the directory entry; the data is reclaimed when the last open handle closes, which is why deleting a large log file being written to does not recover space until the writer restarts.
More examples
Deleting a file that is not there
The missing file gives ENOENT rather than succeeding quietly, while the real file deletes with a null error. Idempotent cleanup therefore has to ignore ENOENT explicitly, or switch to fs.rm(path, { force: true }).
const fs = require('node:fs');
fs.unlink('already-gone.txt', (err) => {
console.log('first attempt:', err && err.code);
fs.writeFileSync('already-gone.txt', 'now it exists');
fs.unlink('already-gone.txt', (err2) => {
console.log('second attempt error:', err2);
console.log('exists:', fs.existsSync('already-gone.txt'));
});
});Directories are not accepted
The unlink fails even though the directory is empty; the exact code differs by platform, which is why the example checks that an error occurred rather than matching a string. fs.rm handles the directory case.
const fs = require('node:fs');
fs.mkdirSync('a-directory', { recursive: true });
fs.unlink('a-directory', (err) => {
console.log('unlink on a directory failed:', err !== null);
console.log('error is an Error:', err instanceof Error);
fs.rm('a-directory', { recursive: true }, (err2) => {
if (err2) throw err2;
console.log('fs.rm removed it:', !fs.existsSync('a-directory'));
});
});Deleting many files in parallel
Only the two .tmp files are removed and keep.txt survives. Promise.all issues the deletions concurrently, which is safe here because each targets a distinct path; running unbounded parallel unlinks over tens of thousands of files can exhaust file descriptors, so batch them in that case.
import fs from 'node:fs';
import fsp from 'node:fs/promises';
async function main() {
fs.mkdirSync('tmpfiles', { recursive: true });
for (const name of ['a.tmp', 'b.tmp', 'keep.txt']) {
fs.writeFileSync(`tmpfiles/${name}`, '');
}
const files = fs.readdirSync('tmpfiles').sort();
console.log('before:', files);
await Promise.all(
files.filter((f) => f.endsWith('.tmp')).map((f) => fsp.unlink(`tmpfiles/${f}`))
);
console.log('after:', fs.readdirSync('tmpfiles').sort());
fs.rmSync('tmpfiles', { recursive: true, force: true });
}
main();
Discussion