Documentation Index
Fetch the complete documentation index at: https://prefab.prefect.io/docs/llms.txt
Use this file to discover all available pages before exploring further.
Raw values make poor display text. A revenue number like 2847500 means nothing without a currency symbol and thousands separators. A decimal like 0.124 needs to be shown as 12.4%. An ISO date like 2025-01-15T14:30:00Z should read “January 15, 2025.”
Pipes handle this formatting. They’re the | in expressions like {{ revenue | currency }} — they take the value on the left, transform it, and return a display-ready result. In Python, pipes are method calls on Rx objects: revenue.currency() compiles to {{ revenue | currency }}.
Here’s a dashboard card that uses several pipes together:
revenue.currency() turns 2847500 into $2,847,500.00. (revenue / target).percent(1) divides first, then formats the result as a percentage with one decimal place. Each pipe method returns a new Rx, so they chain naturally — and the pipe operator has the lowest precedence in the expression grammar, so arithmetic always evaluates before formatting.
| Pipe | Python | Protocol | Result |
|---|
currency | .currency() / .currency("EUR") | {{ x | currency }} / {{ x | currency:EUR }} | $1,234.00 / €1,234.00 |
percent | .percent() / .percent(1) | {{ x | percent }} / {{ x | percent:1 }} | 76% / 75.6% |
number | .number() / .number(2) | {{ x | number }} / {{ x | number:2 }} | 1,234 / 1,234.00 |
round | .round(2) | {{ x | round:2 }} | 3.14 |
abs | .abs() | {{ x | abs }} | 42 |
One thing to watch for: percent multiplies by 100 before formatting. A value of 0.756 becomes 75.6%, not 0.756%. This matches how most APIs and databases store percentages (as decimals), so it usually does what you want. number and currency produce locale-formatted output (en-US by default).
Date and Time
ISO date strings are precise but unreadable. The date and time pipes turn them into human-friendly formats:
| Pipe | Argument | Example Output |
|---|
date | short | 1/15/2025 |
date | (default: medium) | Jan 15, 2025 |
date | long | January 15, 2025 |
time | | 2:30 PM |
datetime | | Jan 15, 2025, 2:30 PM |
Pass an ISO date string as the input value. The time pipe also accepts time-only strings like "14:30".
| Pipe | Argument | Description |
|---|
upper | | Uppercase the string |
lower | | Lowercase the string |
truncate | max length | Clamp to N characters, append ... if truncated |
pluralize | singular word (default item) | Returns the word as-is for count 1, appends s otherwise |
pluralize pairs well with length for labeling dynamic counts: Rx("items").length().pluralize("item") produces {{ items | length | pluralize:'item' }}, which renders as “1 item” or “3 items” depending on the array size.
Array Operations
When state contains a list of objects, you often need to summarize it — count the elements, extract a subset, or join names into a string. Array pipes handle this without any server logic:
The key line is todos.rejectattr("done").length() — it filters out completed items, then counts what’s left. The renderer evaluates the entire chain at runtime, so the “remaining” count stays current as items are checked off.
| Pipe | Argument | Description |
|---|
length | | Number of elements (also works on strings) |
join | separator (default , ) | Join elements into a string |
first | | First element |
last | | Last element |
selectattr | attribute name | Keep items where the attribute is truthy |
rejectattr | attribute name | Remove items where the attribute is truthy |
Chaining
Pipes chain left to right — each pipe receives the output of the one before it. This is how you compose multiple transformations in sequence:
| Python | Protocol |
|---|
name.lower().truncate(20) | {{ name | lower | truncate:20 }} |
todos.rejectattr("done").length() | {{ todos | rejectattr:'done' | length }} |
revenue.currency().upper() | {{ revenue | currency | upper }} |
Each method call returns a new Rx object, so you can keep building. The chain reads naturally left-to-right in both Python and the protocol.
Pipe Arguments
Some pipes take an argument that modifies their behavior. In the protocol, the argument follows a colon: {{ score | percent:1 }}. In Python, it’s a method parameter: .percent(1).
The argument is always a simple token — a number, a string, or an identifier. It cannot be a sub-expression.
| Python | Protocol | Effect |
|---|
.join(" - ") | {{ tags | join:' - ' }} | Join with - separator |
.currency("EUR") | {{ price | currency:EUR }} | Format as euros |
.default("Anonymous") | {{ name | default:Anonymous }} | Fallback value |
.truncate(20) | {{ bio | truncate:20 }} | Limit to 20 characters |
Default Values
A bare literal after | acts as a default value when the left side is null or undefined. In Python, use .default():
| Python | Protocol | Behavior |
|---|
name.default("Anonymous") | {{ name | 'Anonymous' }} | "Anonymous" if name is undefined |
count.default(0) | {{ count | 0 }} | 0 if count is undefined |
This checks specifically for null/undefined — an empty string "" or 0 will not trigger the default. For broader falsy defaults (including empty strings, zero, and false), use the || operator instead:
{{ name || 'Anonymous' }}
See the Context & Variables page for more on how undefined values behave.
Unknown Pipes
If a pipe name isn’t recognized by the renderer, the value passes through unchanged — no error, just unformatted output. This prevents typos from crashing your UI, but it also means a misspelled pipe name will silently produce raw values.