Skip to content

Pipe Operator

Pipes are how you build readable data flow. They take a value on the left and pass it into a transform on the right, which makes cleanup and collection logic much easier to scan.

Basic pipe

|> passes the left-hand value as the first argument to the transform on the right.

ExpressionResultContext
status |> upper"PENDING"{ status: "pending" }

Chaining

Each stage receives the previous result, so the expression reads in the same order as the transformation itself.

ExpressionResultContext
companyName |> trim |> upper"ACME LABS"{ companyName: " Acme Labs " }

Pipes with arguments

The piped value is always the first argument. Any extra arguments go in parentheses after the transform name. When there are no extra arguments, parentheses are optional -- name |> trim and name |> trim() are identical.

ExpressionResultContext
roleCsv |> split(",")["admin", "billing", "ops"]{ roleCsv: "admin,billing,ops" }
requestedDiscount |> clamp(0, maxDiscount)25{ requestedDiscount: 35, maxDiscount: 25 }

Complex pipelines

Most real application rules end up looking like this: filter a collection, project the values you care about, then format the result.

ExpressionResultContext
orders |> filter(.status == "paid") |> map(.id) |> join(", ")"INV-1001, INV-1003"{ orders: [{ id: "INV-1001", status: "paid" }, { id: "INV-1002", status: "draft" }, { id: "INV-1003", status: "paid" }] }

Choose the readable form: if one value is flowing through a sequence of steps, use pipes. If you are combining peer values like min(a, b) or discount(price, pct), a function call is usually clearer.