# readable-stream: Node's streams, cut out of core and pinned in your dependencies

> readable-stream is a userland copy of the Node.js core streams implementation, cut from Node 18.19.0, so a library can ship one streams implementation instead of inheriting whatever the host Node version happens to provide. It is maintained by the Streams Working Group, it is under semantic versioning, and the interesting part is the version lineage, which explains both what you get and what you do not.

**nodejs/readable-stream** — Node-core streams for userland

- Repository: https://github.com/nodejs/readable-stream
- Website: https://nodejs.org/api/stream.html
- Stars: 1,048 · Forks: 243
- Language: JavaScript
- License: NOASSERTION
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/nodejs-readable-stream

## Why a copy of core exists at all

The package describes itself as Node.js core streams for userland, and the README is explicit that this package is a mirror of the streams implementations in Node.js 18.19.0, with the full documentation on the Node.js website for that version. The reason it exists is stated in one sentence, and it is a reason about your users rather than about you. If you want to guarantee a stable streams base regardless of what version of Node you, or the users of your libraries, are using, you use readable-stream only and avoid the `stream` module in Node core, and the README links a blog post on the background. That is a packaging decision about the runtime contract, and it is the reason a great many libraries took a dependency on this package in the first place. Since version 2.0.0 the package follows semantic versioning, which is what makes such a dependency safe to express. The trade is the one that follows from the mirroring model, and the README is upfront about it, since the implementation is a snapshot of one Node version rather than a living copy of the current one.

## The version lineage, which is the whole story

Each major version of the package is a cut from a particular Node release, and the README lists them. Version 4.x is a cut from Node 18 and supports Node 12, 14, 16 and 18 as well as evergreen browsers, and its breaking changes are the combined breaking changes of Node 12 through Node 18, with the release notes linked for each. Version 3.x is a cut from Node 10, supporting Node 6, 8 and 10, evergreen browsers, IE 11 and the latest Safari. Version 2.x is a cut of the stream module from Node 8, supporting all Node versions from 0.8 onwards, evergreen browsers and IE 10 and 11. The consequence for a reader is precise and worth stating plainly. Adopting readable-stream 4 today means running Node 18's stream semantics on whatever Node version you actually have, and it will not pick up changes made to core streams after 18.19.0. For a library that promises stable behaviour across a range of Node versions, that is exactly the deal being struck. For an application that pins its own Node version, it is a downgrade in freshness, and the alternative of using core streams is simpler.

## Migrating is a one line change, with one trap

The usage section says you can swap your `require('stream')` with `require('readable-stream')` without any changes, if you are just using one of the main classes and functions, and shows the shape of the import.

```js
const {
  Readable,
  Writable,
  Transform,
  Duplex,
  pipeline,
  finished
} = require('readable-stream')
```

The trap is stated right after it. `require('stream')` will return `Stream`, while `require('readable-stream')` will return `Readable`, and the README discourages using whatever is exported directly, recommending that you use one of the properties as shown in the example. The difference matters in practice, because code that reaches for `Stream` or for a helper that lives on the `Stream` object will not find it on the userland package, and the failure is a `TypeError` at runtime rather than a warning at install time. The other detail to note is that the destructuring in the example is the whole recommended surface, `Readable`, `Writable`, `Transform`, `Duplex`, `pipeline` and `finished`, which is a smaller API surface than the core module exposes and is worth checking against your code before you flip the import.

## What actually changed in the v3 line, in behaviour terms

The README enumerates the v3 breaking changes as a list of eight, and it is a useful window into how stream semantics have moved. There were error code changes, in several Node pull requests. The rule that `readable` has precedence over flowing changed. Virtual method errors were made consistent, and stream error handling was updated. `writable.end` should return `this`. A readable continues to read when `push('')` is called. A custom inspect was added to `BufferList`. And `readable` is always deferred with `nextTick`. Those last four are the ones that change timing and return values rather than error text, and they are the kind of difference that shows up as a subtly different event ordering in a pipeline rather than as a stack trace. The v4 line inherits the combined changes of Node 12 through Node 18 in the same way. For a maintainer of a library that supports several Node versions, the v3 list is a checklist of the behaviours that differ between the Node 10 cut and the Node 18 cut, which is exactly the information you need when you decide which line to depend on.

## Browsers, bundlers and the one polyfill rule

The browser story is short. You will need a bundler such as browserify, webpack or parcel, and polyfills are no longer required since version 4.2.0, which is a meaningful change for a package that historically shipped its own polyfills. The manifest shows how the browser build is wired, with a `browser` field that maps `util` to the package's own util module and the main index to `lib/ours/browser.js`, so a bundler swaps in the browser specific entry automatically. The `files` array publishes only `lib`, the `LICENSE` and the `README.md`, and the main entry is `lib/ours/index.js`, which is the userland implementation rather than the copied core one. The dependency list is small and familiar, with `abort-controller`, `buffer`, `events`, `process` and `string_decoder`, which are the pieces the core implementation used to assume were present in Node and that a browser or an old runtime may not have.

## How the mirror is produced and how it is tested

The build scripts explain how the mirror stays a mirror. The `build` script is `node build/build.mjs 18.19.0`, with the Node version as an argument, so producing a new cut is a matter of running the script with a different version. The source of truth for the copy lives under `src/`, the compiled output under `lib/`, and the repository separates the two with a `build/` directory holding the generator. The test setup is more interesting than the build. The main `test` script runs tap over two directories, one for the parallel tests and one for the package's own tests, and there are separate runners for browsers, for bundlers, and for a directory called `readable-stream-test` whose runner is named `test:readable-stream-only`, which is the compatibility harness for checking the package against the implementation it replaces. Coverage is measured with c8 through its own configuration file, formatting is prettier over src, lib and test with a separate check mode, and linting is eslint over src with a local plugin directory in the repository. The `examples/` directory has two small programs, one that types on caplock and one called `typer`.

## The Streams Working Group, and what it means for the package

The package is maintained by the Streams Working Group, which oversees the development and maintenance of the Streams API within Node.js, and the README lists that group's responsibilities. They address stream issues on the Node.js issue tracker, author and edit the stream documentation within the Node.js project, review changes to stream subclasses within that project, redirect changes to streams from the Node.js project to this project, assist in the implementation of stream providers within Node.js, recommend versions of readable-stream to be included in Node.js, and message about the future of streams so the community gets advance notice of changes. The named team members are Mathias Buus, Matteo Collina, who publishes a release GPG key, Robert Nagy and Vincent Weevers. The repository also carries a `GOVERNANCE.md` and a `CONTRIBUTING.md`. For a reader, the important consequence is that this is not a personal fork with a maintenance risk. The group that owns the Node.js stream API is the group that owns this package, which is why the mirror is regenerated on a schedule and why the recommended versions for Node core are decided by the same people.

## Conclusion

Adopt readable-stream if you publish a library whose consumers run different Node versions, since pinning one streams implementation is the entire point and the package carries the Streams Working Group behind it. Do not adopt it in an application that controls its own Node version, because you would then be running Node 18 streams semantics on a newer runtime and taking on the divergence yourself. Before you depend on it, check the version lineage, since the 4.x line is a cut from Node 18 and will not pick up core stream changes, and note the export quirk, where `require('readable-stream')` returns `Readable` rather than the `Stream` object that core returns, which breaks code that relies on the default export. The licence is MIT, the current release is 4.8.0 from 2026-10-08, and the last push was the same day.

## FAQ

### What is the difference between readable-stream and the Node.js stream module?

readable-stream is a userland copy of the streams implementation in Node.js 18.19.0, and its purpose is to give you a stable streams base regardless of the Node version you or your library's users run. The core module keeps evolving with Node, while this package stays fixed at the version it was cut from.

### How do I swap core streams for readable-stream?

Replace `require('stream')` with `require('readable-stream')` and destructure the classes you use, for example `Readable`, `Writable`, `Transform`, `Duplex`, `pipeline` and `finished`. Note the README's warning that `require('stream')` returns `Stream` while `require('readable-stream')` returns `Readable`, so avoid relying on the default export.

### Which Node version is readable-stream 4 based on?

Node 18. The README says version 4.x is a cut from Node 18 and supports Node 12, 14, 16 and 18 as well as evergreen browsers. Version 3 is a cut from Node 10, and version 2 is a cut from Node 8.

### Can I use readable-stream in a browser?

Yes, with a bundler such as browserify, webpack or parcel. The README states that polyfills are no longer required since version 4.2.0, and the package maps a `browser` field so bundlers pick up the browser specific entry point automatically.

## Sources

- [Issues](https://github.com/nodejs/readable-stream/issues)
- [nodejs/readable-stream on GitHub](https://github.com/nodejs/readable-stream)
- [Project website](https://nodejs.org/api/stream.html)
- [README](https://github.com/nodejs/readable-stream/blob/main/README.md)
- [Releases](https://github.com/nodejs/readable-stream/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nodejs-readable-stream
