fsPromises.appendFile()
Appends data to the end of a file, creating the file first if it does not exist.
fsPromises.appendFile(path, data[, options])- Added in Node.js v10.0.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$path | string | Buffer | URL | FileHandle | Filename to append to, or an already-open FileHandle. |
$data | string | Buffer | TypedArray | DataView | AsyncIterable | Iterable | Stream | The content to append. |
$optionsoptional | Object | string | Options object, or a string that is taken as the encoding. |
$options.encodingoptional | string | null= 'utf8' | Encoding used when data is a string. |
$options.modeoptional | integer= 0o666 | File mode applied when the file is created. |
$options.flagoptional | string= 'a' | File system flag. Overriding the default 'a' changes this from an append into something else. |
$options.signaloptional | AbortSignal | undefined= undefined | Allows aborting an in-progress appendFile. |
$options.flushoptional | boolean= false | If 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 youab, not two lines. Put the\nin the data yourself. - Every call is a full open/write/close cycle. In a hot loop this is a lot of syscalls — open a
FileHandleonce withfsPromises.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 overridingflag, you probably wantedwriteFile().
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
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.
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.
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.
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