Need help with proposal-pipeline-operator?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

5.2K Stars 85 Forks 117 Commits 58 Opened issues


A proposal for adding the simple-but-useful pipeline operator to JavaScript.

Services available


Need anything else?

Contributors list

ESNext Proposal: The Pipeline Operator

This proposal introduces a new operator

similar to F#, OCaml, Elixir, Elm, Julia, Hack, Clojure, and LiveScript, as well as UNIX pipes and Haskell's
. It's a backwards-compatible way of streamlining chained function calls in a readable, functional manner, and provides a practical alternative to extending built-in prototypes.

⚠ Warning: The details of the pipeline syntax are currently unsettled. There are two competing proposals under consideration. This readme is a minimal proposal, which covers the basic features of the pipeline operator. It functions as a strawman for comparing the tradeoffs of the competing proposals.

Those proposals are as follows:

Babel plugins for both are already underway to gather feedback, although the Hack-pipe plugin has not yet been merged in.

See also the latest presentation to TC39, the proposal wiki and recent GitHub issues for more information.


The pipeline operator is essentially a useful syntactic sugar on a function call with a single argument. In other words,

is equivalent to
64 |> sqrt

This allows for greater readability when chaining several functions together. For example, given the following functions:

function doubleSay (str) {
  return str + ", " + str;
function capitalize (str) {
  return str[0].toUpperCase() + str.substring(1);
function exclaim (str) {
  return str + '!';

...the following invocations are equivalent:

let result = exclaim(capitalize(doubleSay("hello")));
result //=> "Hello, hello!"

let result = "hello" |> doubleSay |> capitalize |> exclaim;

result //=> "Hello, hello!"

Functions with Multiple Arguments

The pipeline operator does not need any special rules for functions with multiple arguments; JavaScript already has ways to handle such cases.

For example, given the following functions:

function double (x) { return x + x; }
function add (x, y) { return x + y; }

function boundScore (min, max, score) { return Math.max(min, Math.min(max, score)); } can use an arrow function to handle multi-argument functions (such as

let person = { score: 25 };

let newScore = person.score |> double |> (_ => add(7, )) |> ( => boundScore(0, 100, _));

newScore //=> 57

// As opposed to: let newScore = boundScore( 0, 100, add(7, double(person.score)) )

Note: The use of underscore

is not required; it's just an arrow function, so you can use any parameter name you like.

As you can see, because the pipe operator always pipes a single result value, it plays very nicely with the single-argument arrow function syntax. Also, because the pipe operator's semantics are pure and simple, it could be possible for JavaScript engines to optimize away the arrow function.

Use of

The current minimal proposal makes

|> await f
an early error, so there is no support currently for
in the pipeline. Each proposal has a different solution to
in a pipeline, so support is planned. Please see the respective proposals for their solutions.

Usage with
partial application syntax

If the partial application proposal (currently a stage 1 proposal) gets accepted, the pipeline operator would be even easier to use. We would then be able to rewrite the previous example like so:

let person = { score: 25 };

let newScore = person.score |> double |> add(7, ?) |> boundScore(0, 100, ?);

newScore //=> 57

Motivating Examples

Transforming Streams/Iterables/AsyncIterables/Observables

There is native syntax for creating Iterables/AsyncIterables:

function* numbers() {
  yield 1
  yield 2
  yield 3

and for consuming them:

for (const number of numbers()) {

But no syntax yet for transforming an Iterable or stream-like object to another. The pipeline operator allows processing of any Iterable or stream-like object in an easy-to-read multi-step pipeline:

const filter = predicate => function* (iterable) {
  for (const value of iterable) {
    if (predicate(value)) {
      yield value

const map = project => function* (iterable) { for (const value of iterable) { yield project(value) } }

numbers() |> filter(x => x % 2 === 1) |> map(x => x + x)

Popular libraries like RxJS currently simulate this through the


Observable.from([1, 2, 3]).pipe(
  filter(x => x % 2 === 1),
  map(x => x + x)

This works quite nicely with RxJS, but native stream objects like

etc. do not have a

method and therefore need to be wrapped with a library like RxJS or IxJS first.

The pipeline operator makes it possible to easily transform these stream representations in a functional way without having to depend on a library like RxJS or IxJS.

For users of these libraries, it saves boilerplate code, especially when having to interop with native stream representations or other libraries: In the example above,

can come from a library like IxJS and be applied on
without having to wrap
in a library-specific object first like

For WHATWG and Node streams (which have a pipe method) it allows transformation with simple functions instead of through more complicated

objects, with easier error handling (Node's
does not forward errors). Since Node 10
also implements
so any transformation function written for AsyncIterables can directly work with NodeJS streams, just like any transformation function written for Iterables can work with arrays or Sets.

Object Decorators

Mixins via

are great, but sometimes you need something more advanced. A decorator function is a function that receives an existing object, adds to it (mutative or not), and then returns the result.

Decorator functions are useful when you want to share behavior across multiple kinds of objects. For example, given the following decorators:

function greets (person) {
  person.greet = () => `${} says hi!`;
  return person;
function ages (age) {
  return function (person) {
    person.age = age;
    person.birthday = function () { person.age += 1; };
    return person;
function programs (favLang) {
  return function (person) {
    person.favLang = favLang;
    person.program = () => `${} starts to write ${person.favLang}!`;
    return person;
} can create multiple "classes" that share one or more behaviors:

function Person (name, age) {
  return { name: name } |> greets |> ages(age);
function Programmer (name, age) {
  return { name: name }
    |> greets
    |> ages(age)
    |> programs('javascript');


Validation is a great use case for pipelining functions. For example, given the following validators:

function bounded (prop, min, max) {
  return function (obj) {
    if ( obj[prop] < min || obj[prop] > max ) throw Error('out of bounds');
    return obj;
function format (prop, regex) {
  return function (obj) {
    if ( ! regex.test(obj[prop]) ) throw Error('invalid format');
    return obj;

...we can use the pipeline operator to validate objects quite pleasantly:

function createPerson (attrs) {
    |> bounded('age', 1, 100)
    |> format('name', /^[a-z]$/i)
    |> Person.insertIntoDatabase;

Usage with Prototypes

Although the pipe operator operates well with functions that don't use

, it can still integrate nicely into current workflows:
import Lazy from 'lazy.js'

getAllPlayers() .filter( p => p.score > 100 ) .sort() |> (_ => Lazy() .map( p => ) .take(5)) |> ( => renderLeaderboard('#my-div', _));


"Real" Mixins have some syntax problems, but the pipeline operator cleans them up quite nicely. For example, given the following classes and mixins:

class Model {
  // ...
let Editable = superclass => class extends superclass {
  // ...
let Sharable = superclass => class extends superclass {
  // ...

... we can use the pipeline operator to create a new class that extends

and mixes
, with a more readable syntax:
// Before:
class Comment extends Sharable(Editable(Model)) {
  // ...
// After:
class Comment extends Model |> Editable |> Sharable {
  // ...

Real-world Use Cases

Check out the Example Use Cases wiki page to see more possibilities.



  • Firefox 58+ has pipeline support behind the
    compile flag

Build Tools

Related proposals

If you like this proposal, you will certainly like the proposal for easier partial application. Take a look and star if you like it!

You may also be interested in these separate proposals for a function composition operator:

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.