Library / SDK
jprichardson/node-fs-extra avatar
jprichardson/node-fs-extra

fs-extra: the Node.js filesystem package that replaces mkdirp, rimraf and ncp

Node.js: extra methods for the fs object like copy(), remove(), mkdirs()

9,590 stars792 forksJavaScriptMIT

At a glance

What is it?
fs-extra wraps Node's fs module with recursive copy, remove, mkdirs and JSON helpers, keeps every native fs method, and routes calls through graceful-fs. It is a convenience layer, not a new filesystem API.
Who is it for?
Adopt fs-extra if your Node code already shells out to mkdirp, rimraf or ncp, or if you want promise-returning filesystem calls without wrapping fs by hand. Skip it if you are on a modern Node runtime and only need mkdir recursive, rm recursive or fs/promises, since the README itself notes that fs-extra is a drop in replacement for fs rather than a replacement for the platform.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 9 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What fs-extra adds to Node's fs module

The README opens with the author's own reason for the package: "I got tired of including mkdirp, rimraf, and ncp in most of my projects." That sentence is the whole product thesis. Node's built-in fs module gives you primitives, but recursive directory creation, recursive deletion, and directory copying each arrived as separate helper packages with their own APIs, their own error behaviour and their own upgrade cadence. fs-extra folds those helpers into one module that also re-exports everything in fs.

The audience is Node developers writing build scripts, CLIs, scaffolding tools, test fixtures and deployment automation. Those are the places where you create a nested output directory, copy a template tree, write a JSON manifest, then delete a staging folder, all in one process. Doing that with raw fs means a mkdir loop with EEXIST handling, a manual recursive unlink, and JSON.parse wrapped in a try block. fs-extra exposes each as a named method.

The method list is split into async and sync halves: copy, emptyDir, ensureFile, ensureDir, ensureLink, ensureSymlink, mkdirp, mkdirs, move, outputFile, outputJson, pathExists, readJson, remove and writeJson on the async side, with a Sync variant of each. mkdirp, mkdirs and ensureDir are documented as the same operation under three names, which is a compatibility decision rather than three capabilities.

One detail worth reading carefully: fs-extra is described as a drop in replacement for fs, and every native method is promisified and copied onto the module. So the package is not a parallel API you adopt instead of fs. It is fs plus extras, and the README says you do not need to require the original module again.

How the drop-in replacement and promise layer actually work

The mechanism visible in the repository is a thin composition layer, not a reimplementation of filesystem syscalls. The package.json lists three runtime dependencies: graceful-fs, jsonfile and universalify. graceful-fs is the piece that handles EMFILE errors, the failure you hit when a process opens more file descriptors than the OS allows and every subsequent open fails. The README states fs-extra uses it to prevent those errors, so the benefit applies to the native methods too, not only the added ones.

jsonfile backs the JSON methods, and universalify is what lets a single exported function accept a callback or return a promise. The README's rule is simple: all fs methods return promises if the callback isn't passed. That is why the same copy call works with .then(), with a Node-style callback, with await, and in a try/catch when you use copySync.

The error model differs by variant and this trips people up. The README states sync methods throw if an error occurs, and async/await throws as well, while callback-style async methods hand the error to the callback. Rejection and throw are not interchangeable in every call site, particularly inside a for loop where you want to collect failures rather than abort.

The ESM path is the sharpest edge. The package.json exports map points "." at ./lib/index.js and "./esm" at ./lib/esm.mjs. The README is explicit that fs methods are not included in fs-extra/esm, so you import fs or fs/promises separately. Its own example shows import { readFileSync } from 'fs' alongside import { outputFile, outputFileSync } from 'fs-extra/esm'. A default import of fs-extra/esm gives you the extra methods only; the README warns that fse.readFileSync is not a function in that case and recommends the regular fs-extra entry point instead.

Installing fs-extra and copying a directory tree

Install with npm. The package declares node >=14.14 in its engines field, so check that first; the README gives no other prerequisites.

bash
npm install fs-extra

The CommonJS entry point is the default. The README notes you no longer need to require fs separately, and suggests naming the variable fse when you want the call sites to make clear which module is in play.

js
const fse = require('fs-extra')

async function build () {
  await fse.copy('/tmp/template', '/tmp/output')
  await fse.outputJson('/tmp/output/manifest.json', { built: true })
}

build().catch(err => console.error(err))

copy takes a source and a destination, and outputJson writes a JSON file, creating parent directories as needed. Both return promises here because no callback is passed. If the paths do not exist or permissions are wrong, the promise rejects and the catch runs.

For a synchronous script, the same work looks like this. The README's own example wraps copySync in try/catch because sync methods throw.

js
const fse = require('fs-extra')

try {
  fse.copySync('/tmp/template', '/tmp/output')
  fse.removeSync('/tmp/output/cache')
} catch (err) {
  console.error(err)
}

If you are on ESM, remember the split import. The README's example imports native reads from fs and the extra writers from fs-extra/esm in the same file.

js
import { readFileSync } from 'fs'
import { readFile } from 'fs/promises'
import { outputFile, outputFileSync } from 'fs-extra/esm'

What you should see after running the copy example is the template tree reproduced under /tmp/output and a manifest.json beside it, with no EEXIST errors on the nested directories.

Where fs-extra is the wrong dependency

The strongest argument against adding fs-extra today is that Node's own fs module absorbed much of what the package was created for. Recursive mkdir and recursive rm now exist natively, and fs/promises provides promise-returning filesystem calls without a wrapper. If your only need is creating a directory path or deleting a tree, fs-extra is a third-party dependency standing in front of a platform feature. The README does not make a case against this; it simply positions the package as a drop in replacement for fs, which means it competes with the standard library rather than complementing it.

Bundling is the second constraint. The package.json marks sideEffects as false and ships only lib/, excluding test directories, which helps tree shaking. But fs-extra still pulls in graceful-fs, jsonfile and universalify. In a serverless bundle or a browser-targeted build, that chain is weight you pay for convenience methods you may never call.

The ESM split is a genuine failure mode, not a stylistic preference. A developer who writes import fse from 'fs-extra/esm' and then calls fse.readFileSync gets a runtime TypeError, because the README states fs methods are not included in that entry point. The fix is either importing fs separately or using the main fs-extra entry, and the README recommends the latter for default exports.

Finally, fs-extra does not watch files and does not report filesystem or device information. The README points to chokidar for watching and to fs-filesystem for device and partition state. Anyone reaching for fs-extra to monitor a directory is using the wrong package, and the documentation says so directly.

fs-extra versus fs/promises and the helper packages it replaced

The honest comparison is not against another npm library but against the standard library plus the three packages fs-extra was built to eliminate. Before it, a typical project depended on mkdirp for recursive directory creation, rimraf for recursive deletion, and ncp for recursive copying, each with a different callback signature and a different error convention. fs-extra's contribution was unifying those under one module that also carries the native fs surface, so a single require covers both the platform methods and the extras.

Node's fs/promises changes the calculus. It gives you promise-based access to the platform API with no additional dependency, and the recursive options on mkdir and rm cover the two most common reasons people installed mkdirp and rimraf. What fs/promises does not give you is a copy method for directory trees, the ensure* family, or the JSON read/write helpers, and it does not route through graceful-fs for EMFILE protection. Those gaps are where fs-extra still earns its place.

A practical split: use fs/promises for reading, writing, stat and simple recursive mkdir or rm, and reach for fs-extra when you need to copy a directory, move a file across devices, ensure a symlink exists, or write JSON with parent directories created automatically. The two can coexist in one file, which is exactly what the README's ESM example demonstrates.

For TypeScript users the README points at the DefinitelyTyped types for fs-extra, since the package itself ships JavaScript. That is a separate dependency and a separate release cadence from the fs-extra package, worth knowing before you assume type definitions track a new fs-extra version immediately.

Maintenance, licensing and what an upgrade costs

The repository is not archived, and the last push was on 2026-09-17, which is four days before this article's reference point. The README does not document a release process, a support window or a deprecation policy, and no recent releases were retrieved, so version 11.4.0 in package.json is the only version fact available here. Treat the changelog file at the repository root as the place to check before upgrading, since the README itself does not carry migration notes.

The licence is MIT, declared in both the README badge and package.json. That permits commercial and closed-source use with the usual requirement to preserve the copyright notice and licence text. This is a summary of what the repository states, not legal advice; if your organisation has specific obligations around attribution in distributed binaries, have counsel read the LICENSE file rather than this paragraph.

The dependencies are the real upgrade surface. graceful-fs, jsonfile and universalify are all caret-ranged, meaning minor and patch updates arrive without a major bump to fs-extra. The engines field pins the floor at node >=14.14, so dropping support for older runtimes is a breaking change for consumers on those versions. The exports map has exactly two entries, "." and "./esm", so any change to that map is a breaking change for ESM consumers.

On the contribution side, the README asks for more tests on edge cases across different platforms and states the project uses JavaScript Standard Style, enforced by the lint script. The test setup runs a lint pass, a unit pass through nyc, and a separate ESM pass through test.mjs, so a change that only works in CommonJS will fail CI. There is no documented rollback procedure in the README if an upgrade misbehaves; the practical path is pinning the previous version in package.json.

Editorial conclusion

Adopt fs-extra if your Node code already shells out to mkdirp, rimraf or ncp, or if you want promise-returning filesystem calls without wrapping fs by hand. Skip it if you are on a modern Node runtime and only need mkdir recursive, rm recursive or fs/promises, since the README itself notes that fs-extra is a drop in replacement for fs rather than a replacement for the platform. Before committing, verify two things: that your Node version is at least 14.14 as declared in the package.json engines field, and that any ESM entry point you plan to use imports fs or fs/promises separately, because the README states fs methods are not included in fs-extra/esm. If your code depends on walk() or walkSync(), check the klaw and klaw-sync packages instead, since those were removed from fs-extra in v2.0.0.

Frequently asked questions

What does fs-extra do that Node's fs module does not?

It adds methods such as copy, remove, ensureDir, outputJson and readJson, and it promisifies the native fs methods so they return promises when no callback is passed. The README describes it as a drop in replacement for fs that also uses graceful-fs to prevent EMFILE errors.

How do I install and require fs-extra?

Run npm install fs-extra, then require it with const fse = require('fs-extra'). The README states you do not need to require the original fs module, because all fs methods are attached to the fs-extra export.

Does fs-extra work with ESM imports?

There is an fs-extra/esm entry point that supports default and named exports, but the README states fs methods are not included there, so you must import fs or fs/promises separately. For default exports the README recommends using regular fs-extra instead.

What Node version does fs-extra require?

The package.json engines field declares node >=14.14 for version 11.4.0. The README also notes that the deprecated constants fs.F_OK, fs.R_OK, fs.W_OK and fs.X_OK are not exported on Node.js v24.0.0 and later, and that the fs.constants equivalents should be used instead.

What happened to walk() and walkSync() in fs-extra?

The README states they were removed in v2.0.0. If you need that behaviour, walk and walkSync are available as the separate klaw and klaw-sync packages.

Official sources

  1. Issues
  2. jprichardson/node-fs-extra on GitHub
  3. License: MIT
  4. README
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jprichardson-node-fs-extra.svg)](https://hysenlabs.com/projects/jprichardson-node-fs-extra)