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 &lt;)
  • 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.