fsPromises.appendFile()

Appends data to the end of a file, creating the file first if it does not exist.

Signature
fsPromises.appendFile(path, data[, options])
  • Added in Node.js v10.0.0

Parameters

ParameterTypeDescription
$pathstring | Buffer | URL | FileHandleFilename to append to, or an already-open FileHandle.
$datastring | Buffer | TypedArray | DataView | AsyncIterable | Iterable | StreamThe content to append.
$optionsoptionalObject | stringOptions object, or a string that is taken as the encoding.
$options.encodingoptionalstring | null= 'utf8'Encoding used when data is a string.
$options.modeoptionalinteger= 0o666File mode applied when the file is created.
$options.flagoptionalstring= 'a'File system flag. Overriding the default 'a' changes this from an append into something else.
$options.signaloptionalAbortSignal | undefined= undefinedAllows aborting an in-progress appendFile.
$options.flushoptionalboolean= falseIf true, the underlying file descriptor is flushed prior to closing it.

Return value

Promise<undefined>Fulfills with undefined upon success.

Errors

Rejects with ENOENT if the parent directory is missing, EACCES on permission failures, and ERR_INVALID_ARG_TYPE if data is not a supported type.

Changelog

  • PHP v21.1.0, v20.10.0The flush option is now supported.
  • PHP v15.14.0, v14.18.0The data argument supports AsyncIterable, Iterable and Stream.
  • PHP v14.0.0The data parameter no longer coerces unsupported input to strings.

fsPromises.appendFile() is writeFile() with the flag pre-set to 'a'. It opens the file, seeks to the end, writes, and closes — creating the file if it is not there. That last part matters: you never need an existence check before appending.

When to reach for it

Use it for accumulating records: a log file, a JSONL event stream, an audit trail, a CSV you build row by row. Anything where the previous contents must survive.

What trips people up

  • It does not add a newline. Two appends of 'a' and 'b' give you ab, not two lines. Put the \n in the data yourself.
  • Every call is a full open/write/close cycle. In a hot loop this is a lot of syscalls — open a FileHandle once with fsPromises.open(path, 'a') and reuse it, or use a write stream.
  • Passing { flag: 'w' } quietly turns the append into a truncating write. If you are overriding flag, you probably wanted writeFile().

On most platforms a single append of a small payload lands atomically at the end, which is why concurrent appenders to one log rarely interleave mid-line — but that is an OS guarantee, not a Node one.

Example

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

When to use it

  • Append a JSONL event record after each request.
  • Grow a CSV export one row at a time.
  • Write an audit trail entry that must not overwrite earlier entries.
  • Create-or-append a log file without checking whether it exists first.

Frequently asked questions

Does fsPromises.appendFile create the file if it does not exist?

Yes — the default 'a' flag creates the file when it is missing. It will not create missing parent directories, though, which still reject with ENOENT.

Why is everything appending onto one line?

Because appendFile writes exactly the bytes you give it and adds no separator. Append data + '\n' yourself.

Is appendFile safe when two processes write to the same log?

In practice small appends to a file opened with 'a' are atomic on Linux and macOS, so lines from different processes do not interleave. Node makes no such guarantee itself, and large writes or network filesystems can still tear.

More examples

Build a JSONL event log

Prints 2 stop. One JSON object per line means the file stays appendable and readable line by line, unlike a single top-level JSON array which would have to be rewritten every time.

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

const events = [{ t: 1, m: 'start' }, { t: 2, m: 'stop' }];
for (const e of events) {
  await fs.appendFile('events.jsonl', JSON.stringify(e) + '\n');
}

const rows = (await fs.readFile('events.jsonl', 'utf8'))
  .trim()
  .split('\n')
  .map(JSON.parse);
console.log(rows.length, rows[1].m);

await fs.rm('events.jsonl');

flag: 'w' silently cancels the append

The second log line shows only "line three\n" — overriding flag replaced the whole file. appendFile's append behaviour lives entirely in its default flag, so touching that option undoes the reason you called it.

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

await fs.appendFile('trace.log', 'line one\n');
await fs.appendFile('trace.log', 'line two\n');
console.log('two appends:', JSON.stringify(await fs.readFile('trace.log', 'utf8')));

await fs.appendFile('trace.log', 'line three\n', { flag: 'w' });
console.log('after flag w:', JSON.stringify(await fs.readFile('trace.log', 'utf8')));

await fs.rm('trace.log');

Append many records through one open handle

Prints 5 record 4. One open and one close for five writes instead of five of each — the pattern to switch to once appending happens on a hot path.

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

const handle = await fs.open('bulk.log', 'a');
try {
  for (let i = 0; i < 5; i++) {
    await handle.write(`record ${i}\n`);
  }
} finally {
  await handle.close();
}

const lines = (await fs.readFile('bulk.log', 'utf8')).trim().split('\n');
console.log(lines.length, lines.at(-1));

await fs.rm('bulk.log');

Discussion

  • Be the first to comment on this lesson.