fs.createWriteStream()

Opens a file as a writable stream for incremental writes without holding the whole output in memory.

Signature
fs.createWriteStream(path[, options])
  • Added in Node.js v0.1.31

Parameters

ParameterTypeDescription
$pathstring | Buffer | URLThe file to write. Ignored when options.fd is supplied.
$optionsoptionalstring | ObjectStream options. If a string is passed it is taken as the encoding.
$options.flagsoptionalstring= 'w'File system flags. 'w' truncates, 'a' appends, 'r+' opens for update without truncating, 'wx' fails if the file exists.
$options.encodingoptionalstring= 'utf8'Encoding used for string writes.
$options.fdoptionalinteger | FileHandle= nullAn already-open descriptor to write to. When given, path is ignored and no 'open' event is emitted.
$options.modeoptionalinteger= 0o666File mode, applied only if the file is created.
$options.autoCloseoptionalboolean= trueClose the descriptor automatically on 'error' or 'finish'. When false you must close it yourself.
$options.emitCloseoptionalboolean= trueEmit a 'close' event after the stream is destroyed.
$options.startoptionalintegerByte offset at which to begin writing. Usually paired with flags: 'r+' to patch a file in place.
$options.fsoptionalObject | null= nullOverrides for open, write, writev and close. At least one of write or writev is required when this is used.
$options.signaloptionalAbortSignal | null= nullAborting the corresponding controller destroys the stream.
$options.highWaterMarkoptionalnumberBuffer threshold above which write() returns false. Defaults to stream.getDefaultHighWaterMark().
$options.flushoptionalboolean= falseFlush the underlying descriptor (fsync) before closing it.
$options.windowsHandleoptionalbigint= nullA raw Win32 HANDLE to write to in place of fd. Windows only; throws on other platforms.

Return value

fs.WriteStreamAn fs.WriteStream, a standard Writable that emits 'open', 'ready', 'drain', 'finish', 'close' and 'error' events and exposes a bytesWritten property.

Errors

A path that cannot be opened does not throw synchronously — it surfaces as an 'error' event carrying EACCES, ENOENT or EEXIST (with flags: 'wx'), and writing after end() emits an ERR_STREAM_WRITE_AFTER_END error rather than throwing at the call site.

Changelog

  • PHP v26.8.0Added the windowsHandle option.
  • PHP v22.0.0The default highWaterMark was raised, changing how often write() returns false and how large each disk write is.
  • PHP v21.0.0, v20.10.0Added the flush option, which fsyncs the descriptor before closing so data is durable rather than merely handed to the OS.
  • PHP v15.5.0Added support for AbortSignal via the signal option.
  • PHP v14.0.0The emitClose default changed to true.
  • PHP v7.6.0The path parameter can be a WHATWG URL object using the file: protocol.
  • PHP v5.5.0The autoClose option is supported.

fs.createWriteStream() returns an fs.WriteStream, a writable stream over a file descriptor. It suits output produced over time — an append-only log, a CSV assembled row by row, a response body being saved — where buffering everything for a single fs.writeFile() would be wasteful or impossible.

The default flags is 'w', which truncates the file the moment the stream opens, even if you never write anything. Use 'a' to append, 'wx' to fail when the file already exists, and 'r+' with start to patch bytes in place.

Ordering and backpressure

write() returns false once the internal buffer exceeds highWaterMark. Ignoring that return value still produces correct, in-order output — but the queue grows in memory without bound, which is how a fast producer becomes an out-of-memory crash. Wait for the 'drain' event before writing more, or hand the stream to stream.pipeline() and let it handle backpressure for you.

Knowing when it is done

Call end(), then wait for 'finish' before reading the file back or reporting success. Reading straight after end() races the flush and frequently sees a short or empty file — one of the most common bug reports against this API.

Example

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

When to use it

  • Append structured entries to an application log without reopening the file each time.
  • Stream a generated CSV or JSON export straight to disk as rows are produced.
  • Save an incoming upload or HTTP response body to a file via stream.pipeline().
  • Patch a fixed-width record in place with flags: 'r+' and a start offset.

Frequently asked questions

How do I know when a write stream has finished writing to the file?

Call end() and then wait for the 'finish' event — or use stream.pipeline(), whose promise resolves at the same point. Reading the file immediately after end() returns races the flush and often shows partial or empty content, since end() only queues the final write.

Why is my file empty or truncated after using createWriteStream?

Almost always because the file was read before 'finish' fired, or because the stream was opened with the default flags: 'w', which truncates the file the instant it opens. Await 'finish' before reading, and use flags: 'a' when you meant to append.

What does it mean when write() returns false?

It means the stream's internal buffer has passed highWaterMark and you should stop writing until the 'drain' event fires. The write is still accepted and ordering is preserved, but continuing to ignore the signal lets the buffer grow unboundedly in memory — which is how a fast loop writing to a slow disk turns into an out-of-memory crash.

More examples

Append instead of truncate

The output is [ 'first', 'second' ] — both lines survive. Swap flags to 'w' and you get [ 'second' ] only, because the stream truncates the file at open time rather than at first write. That timing detail matters: a stream opened with 'w' and then abandoned without a single write still leaves an empty file behind.

Example · javascript
import fs from 'node:fs';
import fsp from 'node:fs/promises';
import { once } from 'node:events';

// Each pass reopens the file. With the default flags: 'w' the second
// pass would wipe the first line; 'a' appends to whatever is there.
for (const msg of ['first', 'second']) {
  const s = fs.createWriteStream('audit.log', { flags: 'a' });
  s.end(msg + '\n');
  await once(s, 'finish');
}

console.log((await fsp.readFile('audit.log', 'utf8')).trimEnd().split('\n'));

await fsp.unlink('audit.log');

Copy a file with pipeline, which handles backpressure for you

It prints 1024, and the await returns only after the write side has finished — so the stat that follows is safe. This is the version to reach for in real code: with a bare .pipe() an error on the read side leaves the write stream open and the destination file truncated, whereas pipeline rejects and cleans up both ends.

Example · javascript
import fs from 'node:fs';
import fsp from 'node:fs/promises';
import { pipeline } from 'node:stream/promises';

await fsp.writeFile('source.bin', Buffer.alloc(1024, 7));

// pipeline waits for 'finish', propagates errors from either side,
// and destroys both streams so no descriptor leaks.
await pipeline(
  fs.createReadStream('source.bin'),
  fs.createWriteStream('copy.bin'),
);

console.log('copied bytes:', (await fsp.stat('copy.bin')).size);

await fsp.unlink('source.bin');
await fsp.unlink('copy.bin');

Discussion

  • Be the first to comment on this lesson.