stream.Writable
Create a destination stream that accepts written chunks and handles them.
new stream.Writable([options])- Added in Node.js v0.9.4
Parameters
| Parameter | Type | Description |
|---|---|---|
$optionsoptional | Object | Configuration for the stream. All fields are optional. |
$options.highWaterMarkoptional | number= See stream.getDefaultHighWaterMark() | Buffer level at which stream.write() starts returning false. |
$options.decodeStringsoptional | boolean= true | Whether to encode strings passed to write() into Buffers before passing them to _write(). Setting it to false keeps strings as strings. |
$options.defaultEncodingoptional | string= 'utf8' | The default encoding used when no encoding is given to write(). |
$options.objectModeoptional | boolean= false | Whether write(anyObj) is a valid operation, allowing JavaScript values other than string, Buffer, TypedArray or DataView. |
$options.emitCloseoptional | boolean= true | Whether the stream should emit 'close' after it has been destroyed. |
$options.writeoptional | Function | Implementation for the writable._write() method, called as write(chunk, encoding, callback). |
$options.writevoptional | Function | Implementation for the writable._writev() method, called as writev(chunks, callback). |
$options.destroyoptional | Function | Implementation for the writable._destroy() method, called as destroy(error, callback). |
$options.finaloptional | Function | Implementation for the writable._final() method, called as final(callback) before 'finish'. |
$options.constructoptional | Function | Implementation for the writable._construct() method, called as construct(callback). |
$options.autoDestroyoptional | boolean= true | Whether this stream should automatically call .destroy() on itself after ending. |
$options.signaloptional | AbortSignal | A 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
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.
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.
const { Writable } = require('node:stream');
const strict = new Writable({
decodeStrings: false,
write(chunk, encoding, callback) {
if (String(chunk).includes('