stream.Writable

Create a destination stream that accepts written chunks and handles them.

Signature
new stream.Writable([options])
  • Added in Node.js v0.9.4

Parameters

ParameterTypeDescription
$optionsoptionalObjectConfiguration for the stream. All fields are optional.
$options.highWaterMarkoptionalnumber= See stream.getDefaultHighWaterMark()Buffer level at which stream.write() starts returning false.
$options.decodeStringsoptionalboolean= trueWhether to encode strings passed to write() into Buffers before passing them to _write(). Setting it to false keeps strings as strings.
$options.defaultEncodingoptionalstring= 'utf8'The default encoding used when no encoding is given to write().
$options.objectModeoptionalboolean= falseWhether write(anyObj) is a valid operation, allowing JavaScript values other than string, Buffer, TypedArray or DataView.
$options.emitCloseoptionalboolean= trueWhether the stream should emit 'close' after it has been destroyed.
$options.writeoptionalFunctionImplementation for the writable._write() method, called as write(chunk, encoding, callback).
$options.writevoptionalFunctionImplementation for the writable._writev() method, called as writev(chunks, callback).
$options.destroyoptionalFunctionImplementation for the writable._destroy() method, called as destroy(error, callback).
$options.finaloptionalFunctionImplementation for the writable._final() method, called as final(callback) before 'finish'.
$options.constructoptionalFunctionImplementation for the writable._construct() method, called as construct(callback).
$options.autoDestroyoptionalboolean= trueWhether this stream should automatically call .destroy() on itself after ending.
$options.signaloptionalAbortSignalA signal representing possible cancellation; aborting it destroys the stream.

Return value

stream.WritableA new writable stream instance.

Errors

Writing to a Writable that implements neither _write() nor _writev() raises ERR_METHOD_NOT_IMPLEMENTED, and writing null in object mode throws ERR_STREAM_NULL_VALUES.

Changelog

  • PHP v22.0.0The default highWaterMark was raised, so write() returns false later than it used to and backpressure tests tuned to 16 KB may no longer trigger.
  • PHP v15.5.0Added the signal option for destroying the stream from an AbortController.
  • PHP v14.0.0The autoDestroy option now defaults to true, so streams destroy themselves after 'finish' and emit 'close'.
  • PHP v10.0.0Added the emitClose option controlling whether 'close' is emitted on destroy.

Writable is the base class for anything that consumes data: files, sockets, HTTP request bodies, and your own sinks. You implement _write(chunk, encoding, callback) — or pass a write function to the constructor — and Node hands you one chunk at a time. Calling callback() tells the stream you are ready for the next one; calling callback(err) fails the stream.

When to reach for it

When something that is not a file or a socket needs to be written to with backpressure: batching rows into a database, forwarding to a logging service, or accumulating output in tests. If you only need the bytes as a value, consuming the source with toArray() is simpler than building a sink.

What trips people up

The callback is not optional. Forget it and the stream accepts one chunk, then goes quiet forever — no error, no 'finish', often just a process that exits with no output. That single omission is the most common Writable bug.

The second is chunk types. By default decodeStrings: true converts strings to Buffers before _write() sees them, so a handler doing chunk.toUpperCase() fails. Set decodeStrings: false to keep strings, or objectMode: true to pass arbitrary values through untouched.

Implement _final(callback) for teardown that must run once after the last chunk, such as flushing a batch.

Example

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

When to use it

  • Batch incoming records and flush them to a database every N rows
  • Collect stream output in a test so assertions can inspect it
  • Forward log lines to a remote sink while honouring backpressure
  • Build a destination that pipeline() can clean up on failure

Frequently asked questions

How do I create a custom writable stream in Node.js?

Extend stream.Writable and implement _write(chunk, encoding, callback), or pass a write function to the constructor, calling callback() after each chunk is handled. Add _final(callback) for work that must run once after the last chunk.

Why does my writable stream hang and never emit finish?

Almost always because _write() did not call its callback, which leaves the stream waiting for permission to deliver the next chunk. Every code path in _write() must end in callback() or callback(err).

Why is chunk a Buffer instead of a string in _write?

Because decodeStrings defaults to true, converting strings to Buffers before _write() sees them. Pass decodeStrings: false to receive strings, or call chunk.toString() inside _write().

More examples

Batching writes in object mode

objectMode lets whole objects through _write() untouched. The output shows flush: [{id:1},{id:2}], flush: [{id:3},{id:4}], then flush: [{id:5}] from _final(), then all flushed. Routing the flush rejection into callback() turns a failed batch into an 'error' event rather than an unhandled rejection.

Example · javascript
const { Writable } = require('node:stream');

function batchWriter(size, flush) {
  let batch = [];
  return new Writable({
    objectMode: true,
    write(row, encoding, callback) {
      batch.push(row);
      if (batch.length < size) return callback();
      const full = batch;
      batch = [];
      Promise.resolve(flush(full)).then(() => callback(), callback);
    },
    final(callback) {
      // _final() runs after the last chunk: flush the partial batch.
      if (batch.length === 0) return callback();
      Promise.resolve(flush(batch)).then(() => callback(), callback);
    },
  });
}

const sink = batchWriter(2, async (rows) => console.log('flush:', rows));
sink.on('finish', () => console.log('all flushed'));

for (const id of [1, 2, 3, 4, 5]) sink.write({ id });
sink.end();

Reporting failure from _write

Prints accepted: fine, then stream failed: null byte not allowed and destroyed: true. Once _write() reports an error the stream is destroyed, so the third write is never delivered. Without the 'error' listener the same failure would crash the process as an uncaught exception.

Example · javascript
const { Writable } = require('node:stream');

const strict = new Writable({
  decodeStrings: false,
  write(chunk, encoding, callback) {
    if (String(chunk).includes('')) {
      // Passing an error to the callback destroys the stream.
      callback(new Error('null byte not allowed'));
      return;
    }
    console.log('accepted:', String(chunk));
    callback();
  },
});

strict.on('error', (err) => {
  console.log('stream failed:', err.message);
  console.log('destroyed:', strict.destroyed);
});

strict.write('fine');
strict.write('badvalue');
strict.write('never reached');

Discussion

  • Be the first to comment on this lesson.