mrkeyoor.com_
Wed 05 Aug 19:55 UTC
npmUtilsupdated 05 Aug 2026

fs-extra

fs-extra is Node's fs module plus the methods people always ended up installing separately: recursive copy, move, remove, mkdir -p, JSON read/write, and ensure-this-path-exists helpers. Every native fs method is re-exported and promisified, so you can import fs-extra instead of fs and never think about it again. It wraps graceful-fs underneath, which retries operations when the process runs out of file descriptors. The project started because its author was tired of pulling in mkdirp, rimraf, and ncp on every project, and it became one of the most depended-upon packages on npm.

Verdict

Still the pragmatic default for Node scripts and build tooling that touch the filesystem a lot, and maintenance is quiet but steady (11.4.0 shipped July 2026 with 12 open issues and PRs). If you are starting a small project on modern Node, check whether native fs.cp and fs.rm already cover you before adding it.

API stability5/5The copy/move/remove/ensure/outputJson surface has been stable for a decade; recent majors mostly dropped old Node versions and removed long-deprecated aliases rather than changing behavior.
Docs4/5Every method has its own markdown doc with examples in the repo, but there is no docs site, and the ESM entry's limitations are documented in one README paragraph people routinely miss.
Maintenance4/5Pushed July 2026 with only 12 open issues and PRs; activity is maintenance-mode (small releases, dependency bumps) rather than feature work, which is fine for a finished tool.
Ecosystem5/5One of the most depended-upon packages on npm with around 210 million weekly downloads; it is the filesystem layer inside a huge share of CLIs and build tools.

Use it if

  • You want copy, move, remove, and emptyDir with sane recursive defaults instead of composing fs.cp, fs.rm, and fs.mkdir flags by hand
  • You read and write JSON files a lot: readJson, writeJson, and outputJson (which creates parent directories) remove a whole class of boilerplate
  • You support a range of Node versions and want one API that behaves the same on Node 14.14 through current, callbacks and promises both
  • You hit EMFILE (too many open files) in scripts that touch thousands of files; the graceful-fs layer queues and retries for you
Skip it if

Setup reality

npm install fs-extra and require it in place of fs; no config, no native builds, and types come from @types/fs-extra if you use TypeScript. The gotchas are around module format: import { readFile } from 'fs-extra/esm' does not include native fs methods, so ESM named imports of plain fs functions fail and you need a separate node:fs import, while a default import of the main entry works fine. On Node 24+ the deprecated fs.F_OK style constants are gone; use fs.constants. Windows symlink tests and operations may need elevated privileges.

Patterns

Copy a file or directory recursivelycopy-file-or-dir

const fs = require('fs-extra')

await fs.copy('/tmp/mydir', '/tmp/copy')
await fs.copy('/tmp/file.txt', '/tmp/copy/file.txt')

Directories copy recursively by default and destination parent directories are created for you. Copying a dir into itself throws.

Copy while skipping some pathscopy-with-filter

const fs = require('fs-extra')

await fs.copy('src', 'dist', {
  filter: (src, dest) => !src.includes('node_modules'),
  overwrite: true
})

filter gets raw paths, not globs; return false to skip. If you return false for a directory, nothing inside it is visited. It can be async.

Move a file or directory across devicesmove-file

const fs = require('fs-extra')

await fs.move('/tmp/somefile', '/storage/somefile', { overwrite: true })

Unlike fs.rename, move falls back to copy-then-delete when source and destination are on different devices or partitions. Without overwrite it errors if the destination exists.

Delete a file or directory treeremove-recursive

const fs = require('fs-extra')

await fs.remove('/tmp/build')
// no error if the path does not exist

Works like rm -rf: recursive and silent on missing paths. On modern Node it delegates to fs.rm under the hood.

Create a directory path if missingensure-dir

const fs = require('fs-extra')

await fs.ensureDir('/tmp/this/path/does/not/exist')
// aliases: fs.mkdirs, fs.mkdirp

mkdir -p semantics: creates every missing parent and succeeds if the directory already exists.

Write a file, creating parent directoriesoutput-file

const fs = require('fs-extra')

await fs.outputFile('/tmp/a/b/c/file.txt', 'hello')

The difference from fs.writeFile is only that missing parent directories are created first; this is the method that replaces the mkdir-then-write dance.

Read and write JSON filesread-write-json

const fs = require('fs-extra')

const pkg = await fs.readJson('./package.json')
await fs.writeJson('./out.json', { name: 'app' }, { spaces: 2 })
await fs.outputJson('./deep/dir/out.json', { ok: true }, { spaces: 2 })

writeJson writes with no whitespace unless you pass spaces. outputJson also creates parent directories. readJson with { throws: false } returns null on parse errors instead of throwing.

Empty a directory without deleting itempty-dir

const fs = require('fs-extra')

await fs.emptyDir('/tmp/some/dir')
// dir now exists and is empty

Deletes contents but keeps (or creates) the directory itself; handy for build output folders where you want the path to survive.

Check whether a path existspath-exists

const fs = require('fs-extra')

if (await fs.pathExists('/etc/passwd')) {
  console.log('found it')
}

The promise-friendly replacement for the deprecated fs.exists. For check-then-act flows, prefer just attempting the operation and catching ENOENT to avoid races.

Ensure a file exists (touch)ensure-file

const fs = require('fs-extra')

await fs.ensureFile('/tmp/logs/app.log')

Creates the file and any missing parent directories if absent; if the file exists it is left untouched, including its content.

Use fs-extra from ESMesm-import

// simplest: default import gets fs + extras
import fs from 'fs-extra'
await fs.outputFile('/tmp/x.txt', 'hi')

// named imports of extras only:
import { outputFile, pathExists } from 'fs-extra/esm'
import { readFile } from 'node:fs/promises'

fs-extra/esm exports only the extra methods; native fs functions are not re-exported there, so import { readFile } from 'fs-extra/esm' is undefined. This is the top ESM confusion with this package.

Pick promise, callback, or sync stylepromises-vs-callbacks

const fs = require('fs-extra')

// promise (no callback passed)
await fs.copy('/tmp/a', '/tmp/b')

// callback
fs.copy('/tmp/a', '/tmp/b', err => { if (err) console.error(err) })

// sync
fs.copySync('/tmp/a', '/tmp/b')

Every async method returns a promise when you omit the callback. Sync variants throw on error; avoid them in servers since they block the event loop.

Alternatives

PackageRegistryPick it when
rimrafnpmYou only need rm -rf semantics, possibly with glob support, and nothing else from the toolbox.
mkdirpnpmYou only need mkdir -p and cannot use native fs.mkdir with recursive: true for some reason.
jsonfilenpmYou only want JSON file read/write; it is the exact module fs-extra uses internally for readJson and writeJson.
cpynpmYou want file copying driven by glob patterns with progress reporting rather than a path-to-path copy.