Skip to main content
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.

Number Formatting

PipePythonProtocolResult
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:
PipeArgumentExample Output
dateshort1/15/2025
date(default: medium)Jan 15, 2025
datelongJanuary 15, 2025
time2:30 PM
datetimeJan 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".

String Formatting

PipeArgumentDescription
upperUppercase the string
lowerLowercase the string
truncatemax lengthClamp to N characters, append ... if truncated
pluralizesingular 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.
PipeArgumentDescription
lengthNumber of elements (also works on strings)
joinseparator (default , )Join elements into a string
firstFirst element
lastLast element
selectattrattribute nameKeep items where the attribute is truthy
rejectattrattribute nameRemove 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:
PythonProtocol
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.
PythonProtocolEffect
.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():
PythonProtocolBehavior
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.