fsPromises.copyFile()

Copies a file to a new path, overwriting the destination unless COPYFILE_EXCL is set.

Signature
fsPromises.copyFile(src, dest[, mode])
  • Added in Node.js v10.0.0

Parameters

ParameterTypeDescription
$srcstring | Buffer | URLSource filename to copy.
$deststring | Buffer | URLDestination filename of the copy operation.
$modeoptionalinteger= 0Optional modifiers controlling the copy, given as a bitwise OR of fs.constants.COPYFILE_EXCL (fail if dest exists), COPYFILE_FICLONE (attempt a copy-on-write reflink, fall back to a real copy) and COPYFILE_FICLONE_FORCE (attempt a reflink, fail if unsupported).

Return value

Promise<undefined>Fulfills with undefined upon success.

Errors

Rejects with ENOENT if src does not exist or dest's parent directory is missing, EEXIST when mode includes COPYFILE_EXCL and dest exists, and EISDIR if either path is a directory.

Changelog

  • PHP v14.0.0Related: fsPromises.cp() was added for copying directory trees; copyFile handles single files only.
  • PHP v10.0.0Added in v10.0.0.

fsPromises.copyFile() copies one file's contents to another path. By default the destination is overwritten if it already exists — the copy is not conditional and does not ask.

When to reach for it

Use it to snapshot a file before mutating it, to stage an asset into a build directory, or to seed a config from a template. It moves the bytes inside the kernel where the platform allows, so it beats readFile-then-writeFile for anything sizeable and never holds the file in your heap.

The mode flags

COPYFILE_EXCL turns the call into create-or-fail, which is the safe choice when you must not clobber. COPYFILE_FICLONE asks for a copy-on-write reflink on filesystems that support it (APFS, Btrfs, XFS) and silently falls back to a byte copy elsewhere; COPYFILE_FICLONE_FORCE rejects instead of falling back.

What trips people up

  • dest must be a filename, not a target directory. Passing a directory rejects with EISDIR rather than copying into it.
  • It copies files only — use fsPromises.cp() with recursive: true for a tree.
  • The destination's parent directory is not created for you.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Snapshot a config file before writing a modified version.
  • Copy a template file into a newly scaffolded project directory.
  • Stage a built asset into a dist folder.
  • Claim a destination filename atomically using COPYFILE_EXCL.

Frequently asked questions

Does fsPromises.copyFile overwrite an existing file?

Yes, by default. Pass fs.constants.COPYFILE_EXCL as the mode to make the call reject with EEXIST instead of clobbering the destination.

How do I copy a whole directory in Node.js?

Use fsPromises.cp(src, dest, { recursive: true }). copyFile handles exactly one file and rejects with EISDIR on a directory.

Why does copyFile fail with EISDIR?

Because dest was a directory. copyFile needs the full destination filename — join the source's basename onto the target directory yourself.

More examples

Timestamped backup before an in-place edit

Prints now: mode=staging and was: mode=production. Copying first means the original survives even if the transform throws halfway through.

Example · javascript
import fs from 'node:fs/promises';

await fs.writeFile('app.conf', 'mode=production\n');

async function editWithBackup(file, transform) {
  await fs.copyFile(file, `${file}.bak`);
  const before = await fs.readFile(file, 'utf8');
  await fs.writeFile(file, transform(before));
}

await editWithBackup('app.conf', (s) => s.replace('production', 'staging'));

console.log('now: ', (await fs.readFile('app.conf', 'utf8')).trim());
console.log('was: ', (await fs.readFile('app.conf.bak', 'utf8')).trim());

await fs.rm('app.conf');
await fs.rm('app.conf.bak');

Copying into a directory needs the filename

Prints directory as dest: EISDIR, then dist contains: [ 'logo.svg' ]. Unlike the cp shell command, copyFile never infers the destination name — path.join with basename is the fix.

Example · javascript
import fs from 'node:fs/promises';
import path from 'node:path';

await fs.mkdir('dist', { recursive: true });
await fs.writeFile('logo.svg', '<svg/>');

try {
  await fs.copyFile('logo.svg', 'dist');
} catch (err) {
  console.log('directory as dest:', err.code);
}

await fs.copyFile('logo.svg', path.join('dist', path.basename('logo.svg')));
console.log('dist contains:', await fs.readdir('dist'));

await fs.rm('dist', { recursive: true });
await fs.rm('logo.svg');

Requesting a copy-on-write clone

Prints sizes match: true 65536, then either the supported or the unsupported line depending on the filesystem. The point is the difference between the two flags: FICLONE degrades quietly, FICLONE_FORCE rejects, so only use FORCE when a reflink is a hard requirement.

Example · javascript
import fs from 'node:fs/promises';
import { constants } from 'node:fs';

await fs.writeFile('data.bin', Buffer.alloc(64 * 1024, 7));

// FICLONE reflinks where supported and falls back silently elsewhere.
await fs.copyFile('data.bin', 'clone.bin', constants.COPYFILE_FICLONE);

const [a, b] = await Promise.all([fs.stat('data.bin'), fs.stat('clone.bin')]);
console.log('sizes match:', a.size === b.size, a.size);

try {
  await fs.copyFile('data.bin', 'forced.bin', constants.COPYFILE_FICLONE_FORCE);
  console.log('reflink supported here');
  await fs.rm('forced.bin');
} catch (err) {
  console.log('reflink unsupported:', err.code);
}

await fs.rm('data.bin');
await fs.rm('clone.bin');

Discussion

  • Be the first to comment on this lesson.