Expressions and Pipes
Expressions inject dynamic data into your templates. They use double-curly syntax and support pipes for transformations.
Variable interpolation
Use {{variable}} to insert a value. Dot-path notation accesses nested fields.
Hello {{user.first_name}},
Your order **#{{order.id}}** totals ${{order.total}}.Variables are resolved from the data object passed at compile time (via --data, --sample, or the SDK's data parameter).
Auto-escaping
All {{expressions}} are auto-escaped for the target channel:
- Email: HTML entities (
<becomes<) - SMS: No escaping needed (plain text)
- Slack: mrkdwn special characters escaped
- Push: No escaping needed (plain text)
- WhatsApp: WhatsApp formatting characters escaped
- Teams: Adaptive Card text escaping
Raw output
Use triple-curly braces {{{expression}}} to output a value without escaping. This is useful when your data contains pre-formatted HTML or channel-native markup.
{{{order.custom_html}}}The linter emits a warning (template/raw-expression) for every raw expression, since unescaped user input is a security risk.
Pipes
Pipes transform values inline. Chain them with |.
{{name | capitalize}}
{{name | uppercase | truncate 20}}Available pipes
String pipes
| Pipe | Description | Example |
|---|---|---|
capitalize |
Capitalize first letter | {{name | capitalize}} — "hello" → "Hello" |
uppercase |
Convert to uppercase | {{name | uppercase}} — "hello" → "HELLO" |
lowercase |
Convert to lowercase | {{name | lowercase}} — "HELLO" → "hello" |
titlecase |
Convert to Title Case | {{name | titlecase}} — "hello world" → "Hello World" |
truncate N |
Truncate to N chars with "..." | {{desc | truncate 50}} |
trim |
Strip whitespace from both ends | {{name | trim}} — " hi " → "hi" |
trim_start |
Strip leading whitespace | {{name | trim_start}} |
trim_end |
Strip trailing whitespace | {{name | trim_end}} |
replace OLD NEW |
Search and replace | {{text | replace "old" "new"}} |
append SUFFIX |
Append a string | {{name | append "@example.com"}} |
prepend PREFIX |
Prepend a string | {{path | prepend "https://"}} |
default VALUE |
Fallback if null | {{nickname | default "Friend"}} |
pluralize S P |
Singular/plural by count | {{n | pluralize "item" "items"}} |
md5 |
MD5 hash (useful for Gravatar) | {{email | md5}} |
split SEP |
Split string into array | {{csv | split ","}} |
Using | default on a nullable field suppresses the template/nullable-access linter warning. The fallback can be a quoted string or a variable path: {{nickname | default user.first_name}}.
Array pipes
| Pipe | Description | Example |
|---|---|---|
count |
Number of items | {{items | count}} — 3 |
join SEP |
Join with separator | {{tags | join ", "}} — "a, b, c" |
unique [KEY] |
Remove duplicates | {{cities | unique}} or {{users | unique "email"}} |
at INDEX |
Item at index | {{emails | at 0}} |
first |
First element | {{items | first}} |
last |
Last element | {{items | last}} |
reverse |
Reverse order | {{items | reverse}} |
sort [KEY] |
Sort ascending | {{items | sort}} or {{users | sort "name"}} |
slice START [LEN] |
Extract a slice | {{items | slice 0 3}} — first 3 items |
slice also works on strings: {{text | slice 0 5}} extracts the first 5 characters.
Math pipes
| Pipe | Description | Example |
|---|---|---|
add N |
Add | {{price | add 5}} — 9 → 14 |
subtract N |
Subtract | {{total | subtract 10}} — 50 → 40 |
multiply N |
Multiply | {{qty | multiply 2}} — 5 → 10 |
divide N |
Divide | {{cents | divide 100}} — 1500 → 15 |
round |
Round to nearest int | {{score | round}} — 12.5 → 13 |
mod N |
Remainder | {{index | mod 2}} — 14 → 2 |
abs |
Absolute value | {{change | abs}} — -10 → 10 |
ceil |
Round up | {{price | ceil}} — 4.1 → 5 |
floor |
Round down | {{price | floor}} — 4.9 → 4 |
Math pipes coerce numeric strings — "42" | add 8 produces 50. Division and modulo by zero return the original value.
Formatting pipes
| Pipe | Description | Example |
|---|---|---|
percent |
Format as percentage | {{ratio | percent}} — 0.85 → "85%" |
number [DECIMALS] |
Decimal places + thousand separators | {{amount | number 2}} — 1234.5 → "1,234.50" |
currency CODE |
Format as currency | {{total | currency "USD"}} — 49.99 → "$49.99" |
currency supports 30+ ISO 4217 codes: USD, EUR, GBP, JPY, INR, CNY, KRW, BRL, CAD, AUD, SGD, CHF, and more. Zero-decimal currencies (JPY, KRW) are handled automatically.
Serialization pipes
| Pipe | Description | Example |
|---|---|---|
json |
Serialize to JSON string | {{user | json}} — {"name":"Alice"} |
from_json |
Parse JSON string to object | {{json_str | from_json}} |
Date pipes
| Pipe | Description | Example |
|---|---|---|
date [FMT] [TZ] |
Format date with optional timezone | {{ts | date "%B %d, %Y"}} |
timezone ZONE |
Convert to IANA timezone | {{ts | timezone "America/New_York"}} |
date uses strftime specifiers. Common: %Y year, %m month, %B month name, %d day, %H hour, %M minute. Parses ISO 8601 / RFC 3339 input. Default format: %Y-%m-%d.
{{order_date | date "%B %d, %Y"}}
"2024-03-15T10:30:00Z" -> "March 15, 2024"
{{ts | date "%I:%M %p" "America/New_York"}}
"2024-03-15T10:30:00Z" -> "06:30 AM"
timezone converts a datetime to a different IANA timezone, returning RFC 3339 output. Chain with date for formatting: {{ts | timezone "Asia/Tokyo" | date "%H:%M"}}.
Variable pipe arguments
Pipe arguments can reference data variables. Bare unquoted words are resolved as variable paths; quoted strings and numbers are literals.
{{item.cost | multiply item.quantity | currency "USD"}} Variable arg
{{name | append " Jr."}} Literal arg (quoted)
{{price | multiply 2}} Literal arg (number)
This lets you express cross-field calculations entirely in the template, without pre-computing values in your data payload. Variable arg resolution follows the same dot-path rules as regular expressions.
Pipe chaining
Pipes can be chained left to right. Each pipe receives the output of the previous one.
{{name | capitalize | truncate 20}}
Unknown pipes
Unknown pipe names are silently ignored -- the value passes through unchanged. This allows forward-compatible pipe usage.
Expressions in frontmatter
Frontmatter fields that support expressions use the same syntax:
---
subject: "Order #{{order.id}} has shipped!"
preheader: "Hey {{user.first_name}}, your order is on its way"
---Expressions in native JSON mode
In .json template files, expressions use Handlebars (a JavaScript templating language) syntax. Pipes become Handlebars helpers:
{
"text": "Hello {{capitalize user.first_name}}, you have {{count items}} items"
}All pipes are available as Handlebars helpers with the same names.