fs is Node.js's built-in module for file system operations, offering low-level access with callback, sync, and promise-based APIs. fs-extra extends fs with additional high-level utilities like recursive copy, directory creation, and JSON helpers, while providing native promise support. fs-extra-promise was a wrapper that added promise support to older versions of fs-extra, but it is now deprecated and should not be used in new projects.
When building Node.js applications that interact with the file system — whether it’s reading configs, writing logs, or managing uploads — choosing the right tooling matters. The built-in fs module is always available, but many developers reach for higher-level packages like fs-extra or (historically) fs-extra-promise. Let’s compare them head-to-head from a practical engineering standpoint.
fs is Node.js’s native file system module. It provides low-level access to file operations with both callback-based and synchronous APIs. As of Node.js v10+, it also includes promise-based versions under fs.promises.
// fs: Callback style
const fs = require('fs');
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});
// fs: Promise style (Node.js >=10)
const { readFile } = require('fs').promises;
const data = await readFile('file.txt', 'utf8');
fs-extra extends fs with additional convenience methods (like copy, remove, ensureDir) and automatically promisifies all functions, so every method returns a promise without needing .promises.
// fs-extra: All methods are promise-friendly by default
const fse = require('fs-extra');
const data = await fse.readFile('file.txt', 'utf8');
await fse.ensureDir('./logs'); // creates dir if missing
await fse.copy('./src', './dest'); // recursive copy
fs-extra-promise was created as a wrapper around fs-extra to add explicit promise support before fs-extra did so natively. However, this package is now deprecated.
⚠️ Important: According to its npm page,
fs-extra-promiseis no longer maintained and explicitly states: "This package is deprecated. Use fs-extra directly instead."
// fs-extra-promise: Deprecated pattern (do not use in new code)
const fsep = require('fs-extra-promise');
// This just wraps fs-extra — redundant since fs-extra supports promises out of the box
Need to create nested directories or delete entire folders? fs requires manual recursion or external logic, while fs-extra handles it natively.
// fs: Manual effort for deep mkdir
const fs = require('fs');
fs.mkdirSync('./a/b/c', { recursive: true }); // supported since Node.js v10.12.0
// But for older versions or more complex cases, you’d need custom code
// fs-extra: Simple and consistent
const fse = require('fs-extra');
await fse.ensureDir('./deeply/nested/path'); // works everywhere
await fse.remove('./folder'); // deletes recursively, even if non-empty
Note: Modern fs does support { recursive: true } in mkdir and rm (since Node.js v14.14.0), but fs-extra’s ensureDir and remove remain more ergonomic and backward-compatible.
Copying a directory tree is non-trivial with raw fs. You’d need to walk the tree manually.
// fs: No built-in copy for directories — you must implement it
// (omitted for brevity — it’s dozens of lines)
// fs-extra: One-liner
await fse.copy('./source', './backup');
This alone makes fs-extra compelling for scripts, build tools, or CLI apps.
Both fs and fs-extra let you read/write JSON, but fs-extra adds dedicated helpers:
// fs: Manual parse/stringify
const data = JSON.parse(await fs.promises.readFile('config.json', 'utf8'));
await fs.promises.writeFile('output.json', JSON.stringify(data, null, 2));
// fs-extra: Built-in
const data = await fse.readJson('config.json');
await fse.writeJson('output.json', data, { spaces: 2 });
Less boilerplate, fewer try/catch blocks for parse errors (it throws cleanly).
Early Node.js only offered callbacks. Developers used libraries like bluebird to promisify fs. Then fs-extra emerged, offering extra methods and promise support via optional wrappers.
Eventually, fs-extra baked in promise support by default (starting around v6+). This made fs-extra-promise obsolete — it was just an unnecessary layer.
Today:
fs.promises gives you native promises, but only for standard fs methods.fs-extra gives you promises plus utility methods (copy, move, emptyDir, etc.).fs-extra-promise adds nothing new and is deprecated.The fs-extra-promise npm page clearly states:
"This package has been deprecated. fs-extra now includes promise support by default. Please use fs-extra directly."
Do not use fs-extra-promise in any new project. If you find it in legacy code, replace it with fs-extra and remove the extra dependency.
You’re on Node.js 18+ and just need to read a JSON config file.
fs.promisesimport { readFile } from 'fs/promises';
const config = JSON.parse(await readFile('app.json', 'utf8'));
You need to empty a dist/ folder, copy static files, and ensure log directories exist.
fs-extraemptyDir, copy, and ensureDir save dozens of lines of error-prone code.import fse from 'fs-extra';
await fse.emptyDir('dist');
await fse.copy('public', 'dist');
await fse.ensureDir('logs');
You inherit a codebase using require('fs-extra-promise').
fs-extra and remove the deprecated package.| Feature | fs (native) | fs-extra | fs-extra-promise |
|---|---|---|---|
| Promise support | ✅ via fs.promises | ✅ built-in | ✅ (but redundant) |
| Recursive mkdir/rm | ✅ (Node.js ≥10/14) | ✅ (ensureDir, remove) | ✅ (via fs-extra) |
| Directory copy/move | ❌ | ✅ | ✅ (via fs-extra) |
| JSON helpers | ❌ | ✅ (readJson, etc.) | ✅ (via fs-extra) |
| Maintenance status | ✅ (core Node.js) | ✅ actively maintained | ❌ deprecated |
| Extra dependencies | ❌ | ✅ (minimal) | ✅ + deprecated wrapper |
fs when you’re in a modern Node.js environment and only need basic file operations. It’s lightweight and dependency-free.fs-extra when you want productivity boosts like recursive copy, safe directory creation, or JSON helpers — especially in scripts, CLIs, or tooling.fs-extra-promise — it’s deprecated and offers no advantage over plain fs-extra.In most real-world Node.js projects beyond trivial examples, fs-extra pays for itself in reduced code complexity and improved reliability. Just remember: it’s a Node.js-only library, so don’t try to bundle it for the browser.
Choose fs-extra when you need robust, high-level file system utilities such as recursive directory copying, safe directory creation (ensureDir), or built-in JSON reading/writing. It’s particularly valuable in scripts, build tools, CLI apps, or any scenario where reducing boilerplate and handling edge cases (like missing parent directories) matters. All methods return promises by default, simplifying async code.
Choose fs if you're working in a modern Node.js environment (v10+) and only need standard file operations like reading, writing, or deleting files. It requires no dependencies and integrates directly with Node.js core, making it ideal for minimal, performance-sensitive applications where you don't need convenience methods.
Do not choose fs-extra-promise for any new project. It is officially deprecated, as fs-extra has included native promise support since version 6. Using it adds an unnecessary dependency and increases maintenance risk. If encountered in legacy code, migrate to fs-extra directly.
fs-extra adds file system methods that aren't included in the native fs module and adds promise support to the fs methods. It also uses graceful-fs to prevent EMFILE errors. It should be a drop in replacement for fs.
I got tired of including mkdirp, rimraf, and ncp in most of my projects.
npm install fs-extra
fs-extra is a drop in replacement for native fs. All methods in fs are attached to fs-extra. All fs methods return promises if the callback isn't passed.
You don't ever need to include the original fs module again:
const fs = require('fs') // this is no longer necessary
you can now do this:
const fs = require('fs-extra')
or if you prefer to make it clear that you're using fs-extra and not fs, you may want
to name your fs variable fse like so:
const fse = require('fs-extra')
you can also keep both, but it's redundant:
const fs = require('fs')
const fse = require('fs-extra')
NOTE: The deprecated constants fs.F_OK, fs.R_OK, fs.W_OK, & fs.X_OK are not exported on Node.js v24.0.0+; please use their fs.constants equivalents.
There is also an fs-extra/esm import, that supports both default and named exports. However, note that fs methods are not included in fs-extra/esm; you still need to import fs and/or fs/promises seperately:
import { readFileSync } from 'fs'
import { readFile } from 'fs/promises'
import { outputFile, outputFileSync } from 'fs-extra/esm'
Default exports are supported:
import fs from 'fs'
import fse from 'fs-extra/esm'
// fse.readFileSync is not a function; must use fs.readFileSync
but you probably want to just use regular fs-extra instead of fs-extra/esm for default exports:
import fs from 'fs-extra'
// both fs and fs-extra methods are defined
Most methods are async by default. All async methods will return a promise if the callback isn't passed.
Sync methods on the other hand will throw if an error occurs.
Also Async/Await will throw an error if one occurs.
Example:
const fs = require('fs-extra')
// Async with promises:
fs.copy('/tmp/myfile', '/tmp/mynewfile')
.then(() => console.log('success!'))
.catch(err => console.error(err))
// Async with callbacks:
fs.copy('/tmp/myfile', '/tmp/mynewfile', err => {
if (err) return console.error(err)
console.log('success!')
})
// Sync:
try {
fs.copySync('/tmp/myfile', '/tmp/mynewfile')
console.log('success!')
} catch (err) {
console.error(err)
}
// Async/Await:
async function copyFiles () {
try {
await fs.copy('/tmp/myfile', '/tmp/mynewfile')
console.log('success!')
} catch (err) {
console.error(err)
}
}
copyFiles()
NOTE: You can still use the native Node.js methods. They are promisified and copied over to fs-extra. See notes on fs.read(), fs.write(), & fs.writev()
walk() and walkSync()?They were removed from fs-extra in v2.0.0. If you need the functionality, walk and walkSync are available as separate packages, klaw and klaw-sync.
fse-cli allows you to run fs-extra from a console or from npm scripts.
If you like TypeScript, you can use fs-extra with it: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/fs-extra
If you want to watch for changes to files or directories, then you should use chokidar.
fs-filesystem allows you to read the state of the filesystem of the host on which it is run. It returns information about both the devices and the partitions (volumes) of the system.
Wanna hack on fs-extra? Great! Your help is needed! fs-extra is one of the most depended upon Node.js packages. This project
uses JavaScript Standard Style - if the name or style choices bother you,
you're gonna have to get over it :) If standard is good enough for npm, it's good enough for fs-extra.
What's needed?
Note: If you make any big changes, you should definitely file an issue for discussion first.
fs-extra contains hundreds of tests.
npm run lint: runs the linter (standard)npm run unit: runs the unit testsnpm run unit-esm: runs tests for fs-extra/esm exportsnpm test: runs the linter and all testsWhen running unit tests, set the environment variable CROSS_DEVICE_PATH to the absolute path of an empty directory on another device (like a thumb drive) to enable cross-device move tests.
If you run the tests on the Windows and receive a lot of symbolic link EPERM permission errors, it's
because on Windows you need elevated privilege to create symbolic links. You can add this to your Windows's
account by following the instructions here: http://superuser.com/questions/104845/permission-to-make-symbolic-links-in-windows-7
However, I didn't have much luck doing this.
Since I develop on Mac OS X, I use VMWare Fusion for Windows testing. I create a shared folder that I map to a drive on Windows.
I open the Node.js command prompt and run as Administrator. I then map the network drive running the following command:
net use z: "\\vmware-host\Shared Folders"
I can then navigate to my fs-extra directory and run the tests.
I put a lot of thought into the naming of these functions. Inspired by @coolaj86's request. So he deserves much of the credit for raising the issue. See discussion(s) here:
First, I believe that in as many cases as possible, the Node.js naming schemes should be chosen. However, there are problems with the Node.js own naming schemes.
For example, fs.readFile() and fs.readdir(): the F is capitalized in File and the d is not capitalized in dir. Perhaps a bit pedantic, but they should still be consistent. Also, Node.js has chosen a lot of POSIX naming schemes, which I believe is great. See: fs.mkdir(), fs.rmdir(), fs.chown(), etc.
We have a dilemma though. How do you consistently name methods that perform the following POSIX commands: cp, cp -r, mkdir -p, and rm -rf?
My perspective: when in doubt, err on the side of simplicity. A directory is just a hierarchical grouping of directories and files. Consider that for a moment. So when you want to copy it or remove it, in most cases you'll want to copy or remove all of its contents. When you want to create a directory, if the directory that it's suppose to be contained in does not exist, then in most cases you'll want to create that too.
So, if you want to remove a file or a directory regardless of whether it has contents, just call fs.remove(path). If you want to copy a file or a directory whether it has contents, just call fs.copy(source, destination). If you want to create a directory regardless of whether its parent directories exist, just call fs.mkdirs(path) or fs.mkdirp(path).
fs-extra wouldn't be possible without using the modules from the following authors:
Licensed under MIT
Copyright (c) 2011-2024 JP Richardson