copyfiles, cpx, fs-extra, and ncp are all Node.js utilities designed to copy files and directories, but they serve different roles in a modern development workflow. fs-extra is a comprehensive drop-in replacement for the native fs module, offering robust promise-based APIs for copying, moving, and ensuring directory existence within JavaScript code. copyfiles and cpx are command-line tools optimized for build scripts; copyfiles focuses on simple glob patterns with flat or structured output, while cpx adds support for watching files and executing commands on change. ncp is an older, asynchronous copying tool that largely lacks modern features like Promise support and active maintenance, making it less suitable for new architectures.
Moving assets, configuration files, and build artifacts is a daily task in frontend engineering. While the native Node.js fs module provides basic tools, it often requires verbose boilerplate for common tasks like recursive copying or ensuring directories exist. The ecosystem offers four distinct solutions: fs-extra, copyfiles, cpx, and ncp. Let's examine how they differ in architecture, usage, and reliability.
The first major distinction is whether the tool is designed to be imported into your code or run as a shell command.
fs-extra is a JavaScript library. You import it directly into your scripts, giving you full programmatic control over error handling and flow.
// fs-extra: Used inside a .js file
const fse = require('fs-extra');
async function copyAssets() {
try {
await fse.copy('./src/assets', './dist/assets');
console.log('Copy successful');
} catch (err) {
console.error('Copy failed', err);
}
}
copyfiles, cpx, and ncp are primarily Command Line Interface (CLI) tools. You invoke them from your terminal or package.json scripts.
# copyfiles: Run in terminal or npm script
npx copyfiles -u 1 "src/assets/**/*" dist
# cpx: Run in terminal with watch mode
npx cpx "src/assets/**/*" dist --watch
# ncp: Run in terminal (legacy)
npx ncp ./src/assets ./dist/assets
In local development, you often need files to sync automatically when you save them. This is where the CLI tools diverge significantly.
cpx excels here with a built-in --watch flag. It keeps the process running and copies files instantly upon detection of changes. This is critical for dev servers that don't bundle static assets.
# cpx: Watches for changes and copies automatically
npx cpx "src/styles/**/*.css" dist/styles --watch
copyfiles does not support watching out of the box. You would need to wrap it with an external tool like nodemon or chokidar-cli to achieve similar behavior, adding unnecessary complexity to your setup.
# copyfiles: Runs once and exits (no watch mode)
npx copyfiles -u 1 "src/styles/**/*.css" dist
# To watch, you must add external tools:
# nodemon --exec "copyfiles ..." --watch src/styles
fs-extra has no built-in watcher either. Since it is a library, you must implement the watching logic yourself using fs.watch or a third-party library, then trigger the copy method manually.
// fs-extra: Manual watch implementation required
const chokidar = require('chokidar');
const fse = require('fs-extra');
chokidar.watch('src/styles').on('change', path => {
fse.copy(path, `dist/${path}`);
});
ncp also lacks watching capabilities and, being unmaintained, does not integrate well with modern event-driven workflows.
How each tool interprets file paths and directory structures affects how you configure your build scripts.
copyfiles uses a unique -u (up) flag to strip directory levels. This is powerful for flattening structures or moving files up a tier without complex rename logic.
# copyfiles: Strip 1 directory level (-u 1)
# src/images/logo.png -> dist/logo.png
npx copyfiles -u 1 "src/images/**/*" dist
cpx relies on standard glob patterns similar to chokidar. It preserves the directory structure relative to the glob match by default, which feels more natural for mirroring folders.
# cpx: Preserves structure relative to match
# src/images/logo.png -> dist/images/logo.png
npx cpx "src/images/**/*" dist
fs-extra requires explicit source and destination paths. It does not parse glob patterns natively; you must combine it with a library like globby if you need to copy multiple disjointed files based on patterns.
// fs-extra: Explicit paths only (no native glob)
const fse = require('fs-extra');
// Copies entire directory tree
await fse.copy('./src/images', './dist/images');
// For globs, you need extra code:
// const files = await globby('src/**/*.png');
// await Promise.all(files.map(f => fse.copy(f, `dist/${f}`)));
ncp handles simple directory-to-directory copying but struggles with complex glob patterns without significant wrapper code.
# ncp: Simple directory copy only
npx ncp ./src/images ./dist/images
Modern Node.js development relies heavily on async/await. The level of native Promise support varies wildly among these packages.
fs-extra was built with Promises in mind. Every method returns a Promise if no callback is provided, making it seamless to use in modern async functions.
// fs-extra: Native Promise support
async function migrate() {
await fse.ensureDir('./dist/data'); // Creates dir if missing
await fse.copy('./data.json', './dist/data/config.json');
}
copyfiles and cpx are CLI tools, so Promise support is irrelevant for direct usage. However, if you try to require them as modules in Node.js code, their APIs are often callback-based or return streams, which can be awkward to integrate into modern promise chains without wrapping.
ncp is strictly callback-based. It does not support Promises natively. You must use util.promisify to make it work with await, which adds friction and potential for errors.
// ncp: Requires manual promisification
const { promisify } = require('util');
const ncp = require('ncp');
const ncpPromise = promisify(ncp.ncp);
// Verbose compared to fs-extra
await ncpPromise('./src', './dist');
Trust is a key factor in architectural decisions. You need to know the tool will work with future Node.js versions.
fs-extra is actively maintained, widely adopted in the enterprise, and considered the de facto standard for file operations in the Node.js ecosystem. It handles edge cases (like permissions and open files) robustly.
cpx and copyfiles are stable CLI utilities. While their update frequency is lower than libraries, they solve specific, narrow problems effectively and have no known critical deprecation warnings.
ncp is effectively deprecated. It has not seen significant updates in years, lacks active maintenance, and its feature set is entirely superseded by fs-extra. Using it introduces unnecessary risk and technical debt.
| Feature | fs-extra | cpx | copyfiles | ncp |
|---|---|---|---|---|
| Type | Library (JS) | CLI Tool | CLI Tool | CLI Tool / Library |
| Promise Support | β Native | N/A (CLI) | N/A (CLI) | β (Needs promisify) |
| Watch Mode | β (Manual setup) | β Built-in | β (External needed) | β |
| Glob Patterns | β (Needs globby) | β Native | β Native | β (Limited) |
| Path Flattening | β (Manual logic) | β | β
(-u flag) | β |
| Status | π’ Active Standard | π’ Stable | π’ Stable | π΄ Legacy/Unmaintained |
fs-extra is your go-to solution for programmatic control. If you are writing build scripts, plugins, or server-side logic where error handling and directory creation matter, this is the only professional choice. It replaces the native fs module entirely.
cpx is the specialist for development workflows. If your npm run dev script needs to sync static assets while you code, cpx saves you from configuring complex watchers. It is the pragmatic choice for "copy and watch" scenarios.
copyfiles serves well for simple, one-time build steps. If you just need to move a folder of images to a dist directory during a production build and don't need watching, its lightweight nature and path-stripping flags are convenient.
ncp should be avoided. There is no compelling reason to use it in a modern stack when fs-extra offers better safety, Promise support, and active maintenance.
Final Thought: For most frontend architectures, a combination of fs-extra (for complex Node scripts) and cpx (for dev-server asset syncing) provides the most robust and maintainable file handling strategy.
Choose copyfiles when you need a lightweight, zero-dependency CLI tool for simple file copying tasks in your package.json scripts. It is ideal for one-off build steps where you need to flatten directory structures or move specific file types without writing custom Node.js code. Avoid it if you need file watching or complex transformation logic during the copy process.
Choose cpx if your workflow requires copying files that update frequently, such as during local development with a bundler. Its built-in --watch mode makes it superior to copyfiles for keeping asset folders in sync without restarting your dev server. It is also the better choice if you need to run a command automatically after files are copied.
Choose fs-extra when you need to perform file operations programmatically within your application or build scripts rather than via shell commands. It is the industry standard for reliable file handling because it adds promise support, ensureDir, and atomic operations to the native fs module. Use this for complex logic where you need to verify paths, handle errors gracefully, or chain multiple file system actions.
Do not choose ncp for new projects. While it was once a popular asynchronous copier, it is no longer actively maintained and lacks native Promise support, forcing you to wrap it in utilities like util.promisify. Its functionality is fully covered and surpassed by fs-extra, which offers better error handling and modern API design.
copy files easily
npm install copyfiles -g
Usage: copyfiles [options] inFile [more files ...] outDirectory
Options:
-u, --up slice a path off the bottom of the paths [number]
-a, --all include files & directories begining with a dot (.) [boolean]
-f, --flat flatten the output [boolean]
-e, --exclude pattern or glob to exclude (may be passed multiple times)
-E, --error throw error if nothing is copied [boolean]
-V, --verbose print more information to console [boolean]
-s, --soft do not overwrite destination files if they exist [boolean]
-F, --follow follow symbolink links [boolean]
-v, --version Show version number [boolean]
-h, --help Show help [boolean]
copy some files, give it a bunch of arguments, (which can include globs), the last one is the out directory (which it will create if necessary). Note: on windows globs must be double quoted, everybody else can quote however they please.
copyfiles foo foobar foo/bar/*.js out
you now have a directory called out, with the files foo and foobar in it, it also has a directory named foo with a directory named bar in it that has all the files from foo/bar that match the glob.
If all the files are in a folder that you don't want in the path out path, ex:
copyfiles something/*.js out
which would put all the js files in out/something, you can use the --up (or -u) option
copyfiles -u 1 something/*.js out
which would put all the js files in out
you can also just do -f which will flatten all the output into one directory, so with files ./foo/a.txt and ./foo/bar/b.txt
copyfiles -f ./foo/*.txt ./foo/bar/*.txt out
will put a.txt and b.txt into out
if your terminal doesn't support globstars then you can quote them
copyfiles -f ./foo/**/*.txt out
does not work by default on a mac
but
copyfiles -f "./foo/**/*.txt" out
does.
You could quote globstars as a part of input:
copyfiles some.json "./some_folder/*.json" ./dist/ && echo 'JSON files copied.'
You can use the -e option to exclude some files from the pattern, so to exclude all all files ending in .test.js you could do
copyfiles -e "**/*.test.js" -f ./foo/**/*.js out
Other options include
-a or --all which includes files that start with a dot.-s or --soft to soft copy, which will not overwrite existing files.-F or --follow which follows symbolinksalso creates a copyup command which is identical to copyfiles but -up defaults to 1
var copyfiles = require('copyfiles');
copyfiles([paths], opt, callback);
takes an array of paths, last one is the destination path, also takes an optional argument which the -u option if a number, otherwise if it's true it's the flat option or if it is an object it is a hash of the various options (the long version e.g. up, all, flat, exclude, error, verbose, follow, and soft)
when the src/dest path start with tilde for home directory under windows, please make sure -u or -f is added in options or use copyup command. if not you will get Error: Illegal characters in path.