← Files Email LoveARCHIVED FILE

skills/iterable-handlebars/references/helpers.md

16.5 KB · Oct 3, 2026 · 06:31 UTC

↓ Download file

# Iterable Handlebars — Helper Reference

Iterable runs **handlebars.java (jknack)**. The helpers below are the ones Iterable actually ships. Argument order matters and is not consistent between helpers — check it here rather than guessing.

## Contents

1. [Syntax primitives](#1-syntax-primitives)
2. [Fallbacks and truthiness](#2-fallbacks-and-truthiness)
3. [Conditional logic](#3-conditional-logic)
4. [Text helpers](#4-text-helpers)
5. [Math and number helpers](#5-math-and-number-helpers)
6. [Date and time helpers](#6-date-and-time-helpers)
7. [Loops and array helpers](#7-loops-and-array-helpers)
8. [Encoding and hashing](#8-encoding-and-hashing)
9. [Variables, lookup, send control](#9-variables-lookup-send-control)
10. [What Iterable does NOT support](#10-what-iterable-does-not-support)
11. [Whitespace control](#11-whitespace-control)

---

## 1. Syntax primitives

| Element | Purpose |
|---|---|
| `{{ }}` | Merge tag, HTML-escaped — **the default for every value that came from data**, URLs and product names included |
| `{{{ }}}` | Merge tag, raw — for markup you authored (snippets, your own HTML fields). Never for profile, event, catalog, feed, or webhook values. See `troubleshooting.md` §4 |
| `[ ]` | Field names with spaces/periods/leading digits; array indices |
| `[[ ]]` | Data feed values (when feed and user contexts are NOT merged) |
| `#` / `/` | Open / close a block helper |
| `.` | Nested field access |
| `' '` | String literals inside math ops, and inside double-quoted HTML attributes |
| `( )` | Subexpressions — **supported** |
| `as \|name\|` | Block parameters — **supported** |

```handlebars
{{fieldName}}                  <!-- user profile or event field -->
{{profile.fieldName}}          <!-- force the profile value over an event field -->
{{objectName.fieldName}}       <!-- nested -->
{{[First Name]}}               <!-- space in name -->
{{[1stName]}}                  <!-- leading digit -->
{{[User Signed Up.First Name]}} <!-- period in name -->
{{shoppingCartItems.[1].name}} <!-- array index, zero-based -->
{{shoppingCartItems.size}}     <!-- array length -->
```

**Precedence:** at send time, if the triggering event or API call contains a field with the same name as a profile field, the **event value wins**. Use `{{profile.fieldName}}` to force the profile value.

**Field names are case-sensitive.**

**Quote rule:** inside an HTML attribute or JSON value already wrapped in double quotes, use single quotes for string literals in the expression.

```handlebars
<img src="{{defaultIfEmpty product.imageUrl 'https://cdn.example.com/fallback.png'}}">
```

---

## 2. Fallbacks and truthiness

```handlebars
{{defaultIfEmpty fieldName "fallback value"}}
```

Argument order is `value` then `fallback`. Fires on `null`, `undefined`, and empty string. Nestable as a subexpression:

```handlebars
{{#ifEq (defaultIfEmpty selectedCity city) "New York"}}I ♥ NY{{/ifEq}}
```

**Falsy values in Iterable:** `null`, `""`, `[]`, `false`, and any zero (`0`, `0.0`). Everything else is truthy. Note that `0` being falsy bites on quantity/count fields.

Inline `yes=`/`no=` fallbacks work on the non-block comparison helpers:

```handlebars
{{eq  a b yes="Same"        no="Different"}}
{{and a b yes="Both true"   no="Not both"}}
{{or  a b yes="At least one" no="Neither"}}
{{not a   yes="False"       no="True"}}
```

---

## 3. Conditional logic

```handlebars
{{#if fieldName}}content{{else}}alternate{{/if}}
{{#if likesCats}}Cats{{else if likesDogs}}Dogs{{/if}}
{{#unless fieldName}}content{{/unless}}

{{#and a b}}…{{else}}…{{/and}}
{{#or  a b}}…{{else}}…{{/or}}
{{#not a}}…{{else}}…{{/not}}

{{#ifEq   a b}}…{{else}}…{{/ifEq}}      <!-- works for strings AND numbers -->
{{#ifGt   a b}}…{{/ifGt}}
{{#ifGte  a b}}…{{/ifGte}}
{{#ifLt   a b}}…{{/ifLt}}
{{#ifLte  a b}}…{{/ifLte}}
{{#ifModEq fieldName divisor remainder}}…{{/ifModEq}}
{{#ifContainsStr string "substring"}}…{{/ifContainsStr}}
{{#ifMatchesRegexStr fieldName "pattern"}}…{{else}}…{{/ifMatchesRegexStr}}

{{#and likesCats (gte age 18)}}Adult cat lover{{/and}}   <!-- subexpression -->
```

Naming convention: `#if`-prefixed helpers are **block-only**. Bare `eq`/`gt`/`gte`/`lt`/`lte`/`and`/`or`/`not` work as blocks *and* as inline value helpers with `yes=`/`no=`.

### Null landmines — these skip the send

- `#lt`, `lt`, `#lte`, `lte`, `#gt`, `gt`, `#gte`, `gte` referencing a **non-existent or null field** → template fails, message not sent to that user.
- `#ifContainsStr` referencing an **empty or missing field** → template fails, message not sent.

Guard with an outer `{{#if fieldName}}` or pass through `defaultIfEmpty` first.

```handlebars
{{#if lifetimeValue}}{{#ifGt lifetimeValue 500}}VIP{{/ifGt}}{{/if}}
{{#ifGt (defaultIfEmpty lifetimeValue 0) 500}}VIP{{/ifGt}}
```

`#ifEq` is safe against nulls — prefer it when you only need equality.

### Regex behaviour

- Case-**sensitive**, no modifier flags (`/i` unsupported).
- Matches the **full string** — use `.*camera.*` to match a substring.
- `{{^ifMatchesRegexStr}}` inverse block form is **not supported**; use `{{else}}` or `{{#unless}}`.

---

## 4. Text helpers

| Helper | Signature | Result |
|---|---|---|
| `length` | `{{fieldName.length}}` | Character count (property access, not a call) |
| `eq` | `{{eq a b}}` | Exact string match |
| `lt` / `gt` | `{{lt a b}}` | Alphabetical before / after |
| `capitalizeFirst` | `{{capitalizeFirst fieldName}}` | Sentence case — first word only |
| `capitalize` | `{{capitalize fieldName}}` | Title case — first letter of each word |
| `upper` | `{{upper fieldName}}` | UPPERCASE |
| `lower` | `{{lower fieldName}}` | lowercase |
| `cut` | `{{cut fieldName "text"}}` | Removes all instances of "text" |
| `replace` | `{{replace fieldName "searchFor" "replaceWith"}}` | Replaces all occurrences |
| `abbreviate` | `{{abbreviate fieldName length}}` | Truncate with ellipsis |
| `center` | `{{center fieldName size=# pad=character}}` | Pad to width |
| `slugify` | `{{slugify fieldName}}` | lowercase-hyphenated |
| `substring` | `{{substring fieldName startIndex endIndex}}` | Slice |
| `join` | `{{join arrayField ", " prefix="Start - " suffix=" - End"}}` | Array → string; `prefix`/`suffix` optional |
| `#breaklines` | `{{#breaklines}}{{fieldName}}{{/breaklines}}` | `\n` → `<br>` |

`abbreviate` is the one to reach for on product names in a constrained layout.

---

## 5. Math and number helpers

```handlebars
{{math fieldName '+' 2}}     <!-- operator is the MIDDLE arg and must be single-quoted -->
{{math fieldName '-' 2}}
{{math fieldName '*' 3}}
{{math fieldName '/' 2}}
{{math fieldName '%' 2}}
```

### numberFormat

```handlebars
{{numberFormat price "currency"}}                       <!-- 29.99 → $29.99 -->
{{numberFormat price "currency" "fr_FR"}}               <!-- → 29,99 € -->
{{numberFormat rate "percent"}}                         <!-- 0.23 → 23% -->
{{numberFormat n "integer"}}
{{numberFormat n "pattern"}}                            <!-- 1234.5678 → 1,234.57 -->
{{numberFormat n maximumFractionDigits=3}}
{{numberFormat n minimumFractionDigits=5}}
{{numberFormat n groupingUsed=true}}                    <!-- 200000000 → 200,000,000 -->
{{numberFormat n "integer" roundingMode="half_up"}}
```

`roundingMode` accepts: `up`, `down`, `ceiling`, `floor`, `half_up`, `half_down`, `half_even`.

Use `numberFormat … "currency"` for any price you display. Raw `{{price}}` prints `29.9` for a value of 29.90, which looks wrong in an inbox.

### Inline numeric comparisons

```handlebars
{{eq  a b yes="X" no="Y"}}
{{gt  a b yes="X" no="Y"}}
{{gte a b yes="X" no="Y"}}
{{lt  a b yes="X" no="Y"}}
{{lte a b yes="X" no="Y"}}
```

(Same null landmine as the block forms — see §3.)

---

## 6. Date and time helpers

**Input dates must be ISO 8601.**

```handlebars
{{dateFormat myDate format="full"}}     <!-- Tuesday, June 19, 2017 -->
{{dateFormat myDate format="long"}}     <!-- June 19, 2017 -->
{{dateFormat myDate format="medium"}}   <!-- Jun 19, 2017 -->
{{dateFormat myDate format="short"}}    <!-- 6/19/17 -->
{{dateFormat myDate "long" "de_DE"}}    <!-- positional: format, locale -->
{{dateFormat myDate format="yyyy-MM-dd HH:mm:ss Z" tz="America/Denver"}}
{{dateFormat myDate tz="userTimezoneField"}}  <!-- tz accepts a TZ code OR a field holding one -->
```

Pattern letters: `y|yy|yyyy` year · `M|MM|MMM|MMMM` month (4, 04, Apr, April) · `d|dd` day of month · `D|DD|DDD` day of year · `E|EEEE` day of week (Tue, Tuesday) · `H` hour 0–23 · `m` minute · `s` second · `z` timezone name (PST) · `Z` numeric offset.

### dateMath

Operators: `+` add, `-` subtract, `/` round. Units chain in one string: `y M w d h m s`.

```handlebars
{{dateMath dateField "+1y-1M+1w-1d+1h-1m+1s"}}
{{dateMath dateField "-5h" format="yyyy-MM-dd HH:mm:ss Z" tz="America/New_York"}}
{{dateMath "now" "-24h" format="yyyyMMddHHmmss"}}
```

### now / timestamp

```handlebars
{{now}}                   <!-- built-in merge tag: "Oct 24, 2024" -->
{{now format="yyyy"}}     <!-- current year, evaluated at send time -->
{{now format="EEEE"}}     <!-- Tuesday -->
{{timestamp}}             <!-- epoch milliseconds -->
```

### Comparing dates — the format trap

Date comparison requires a **numeric-only** format string. Any hyphen, slash, or space breaks the comparison silently. Use `yyyyMMddHHmmss`, and pin both sides to the same timezone.

```handlebars
{{#ifGte (dateFormat signupDate format="yyyyMMddHHmmss" tz="UTC")
         (dateMath "now" "-1M" format="yyyyMMddHHmmss" tz="UTC")}}
  Signed up within the last month
{{/ifGte}}
```

Days-between idiom:

```handlebars
{{dateMath myDateField (now format="-y'y'+1'y'-M'M'+1'M'-d'd'+1'd'-H'H'-m'm'-s's'" tz="UTC") format="D"}}
```

---

## 7. Loops and array helpers

```handlebars
{{#each arrayName}}
  <div>{{fieldName}}</div>
{{/each}}

{{#each shoppingCartItems}}
  {{#if @first}}First item: {{name}}{{/if}}
  {{#if @last}}Last item: {{name}}{{/if}}
  Item {{math @index '+' 1}}: {{name}}          <!-- @index is zero-based -->
{{/each}}

{{#each objectName}}
  {{@key}} = {{this}}                            <!-- iterating an object -->
{{/each}}

{{#each rows}}
  {{#ifModEq @index 2 0}}<tr class="alt">{{/ifModEq}}   <!-- zebra striping -->
{{/each}}
```

### Limiting a loop

There is no `limit` argument. Nest a comparison on `@index`:

```handlebars
{{#each recommendations}}
  {{#lt @index 4}}
    <td>{{name}}</td>
  {{/lt}}
{{/each}}
```

### Array helpers

```handlebars
{{arrayName.size}}

{{#ifContains shoppingCartItems '{"productName":"chips"}'}}Chips in cart!{{/ifContains}}

{{#minInList shoppingCartItems "price"}}Cheapest: {{price}}{{/minInList}}
{{#maxInList shoppingCartItems "price"}}Priciest: {{price}}{{/maxInList}}

{{#eq  array1 array2}}Equal{{/eq}}
{{#neq array1 array2}}Not equal{{/neq}}

{{#sortBy arrayName field="price" order="asc" as |sorted|}}
  {{#each sorted}}<div>{{name}} — {{numberFormat price "currency"}}</div>{{/each}}
{{/sortBy}}

{{#groupBy arrayName field="category" as |groups|}}
  {{#each groups as |group|}}
    <h3>{{group.key.category}} ({{group.count}})</h3>
    {{#each group.items as |product|}}<div>{{product.name}}</div>{{/each}}
  {{/each}}
{{/groupBy}}
```

`sortBy` `order` accepts `asc` / `desc`. `groupBy` groups a **maximum of 100 items** — beyond that only the first 100 are included. `#ifContains` takes a single-quoted JSON string for object matching.

---

## 8. Encoding and hashing

```handlebars
{{#urlEncode}}{{fieldName}}{{/urlEncode}}   <!-- block form only -->
{{toJson fieldName}}
{{toUrlEncodedJson fieldName}}
{{#base64}}{{fieldName}}{{/base64}}         <!-- block form only -->
{{hexEncode fieldName}}  <!-- also available as a block -->
{{md5 fieldName}}
{{sha1 fieldName}}
{{sha256 fieldName}}
{{hmacSHA1 fieldName}}    <!-- key comes from the project's HMAC secret -->
{{hmacSHA256 fieldName}}

{{#sha1}}{{userName}}@{{host}}{{/sha1}}     <!-- block form concatenates before hashing -->
```

`urlEncode` applies standard URL formatting — spaces become `+`, special characters become their ASCII escapes. Wrap **every dynamic value that lands in a query string**; escaping alone does not URL-encode.

```handlebars
https://example.com/preferences?token={{#urlEncode}}{{prefToken}}{{/urlEncode}}&campaignId={{#urlEncode}}{{campaignId}}{{/urlEncode}}
https://example.com/search?q={{#urlEncode}}{{lastSearchTerm}}{{/urlEncode}}
```

(`prefToken` is an opaque, short-lived signed token synced to the profile — put that in the URL, never the email address or user ID: URL-encoding formats an identifier but does not stop it reaching logs, analytics, history, and referrers.)

Because spaces become `+`, it is right for a query value and wrong for a path segment, where `+` stays a literal plus. `toJson` is the encoding for a value inside `<script>` or a JSON body — HTML escaping does not make a value JSON-safe, and neither does turning it off. `toUrlEncodedJson` is the URL-safe variant. Check any encoded value in Preview: a helper stacked on already-escaped output can double-encode, and Preview shows it straight away.

---

## 9. Variables, lookup, send control

```handlebars
{{#assign "myVar"}}Iterable{{/assign}}
Greetings from {{myVar}}

{{#lookup greetings language as |lang|}}{{lang.greeting_1}}{{/lookup}}
{{#lookup greetings email resolveKey=false as |row|}}…{{/lookup}}
<!-- resolveKey=false when the key itself contains periods, e.g. an email address -->

{{#ifLt creditAvailable product.price}}
  {{sendSkip cause="insufficient credit" creditAvailable=creditAvailable creditRequired=product.price}}
{{/ifLt}}
```

`{{#assign}}` is the documented way to hoist a value computed outside a loop into the loop body, since `../` parent-scope access is undocumented in Iterable (see §10).

`{{sendSkip}}` deliberately aborts the send; the event is logged with reason `SendAborted`. Use it when sending would be worse than not sending — no inventory, no valid offer, a broken feed.

---

## 10. What Iterable does NOT support

**Documented as unsupported:**

- Inverse block sections `{{^helperName}}` — use `{{else}}` or `{{#unless}}`.
- Regex modifier flags in `ifMatchesRegexStr`.
- Periods in user/event field names — Handlebars reads `.` as property access.

**Absent from all Iterable documentation — treat as unavailable and route around:**

| Standard Handlebars | Iterable substitute |
|---|---|
| `{{#with}}` | Block params: `{{#lookup … as \|x\|}}`, `{{#catalog … as \|item\|}}` |
| Partials `{{> name}}` | `{{snippet "name"}}` |
| Custom registered helpers | No mechanism — restructure or use a data feed |
| `../` parent scope | `{{#assign}}` before the loop, or block params |
| `{{#each}}…{{else}}` empty fallback | `{{#if array}}{{#each array}}…{{/each}}{{else}}…{{/if}}` |
| `{{lookup a b}}` (JS-style) | Iterable's `#lookup` is a **block** helper with different semantics |

`<!-- comment -->` is standard Handlebars and works in handlebars.java, but Iterable does not document it. In HTML template bodies the documented-safe choice is an HTML comment `<!-- … -->`, which also survives the WYSIWYG editor. Note HTML comments ship in the sent message and count toward Gmail's ~102 KB clipping threshold, so keep them short.

**Confirmed supported:** subexpressions `( )`, block params `as |x|`, `{{else if}}`.

---

## 11. Whitespace control

Whitespace — spaces, tabs, newlines — is **preserved by default**, which breaks URLs, deep links, and JSON payloads.

| Action | Syntax |
|---|---|
| Strip leading whitespace | `{{~tag}}` |
| Strip trailing whitespace | `{{tag~}}` |
| Strip both | `{{~tag~}}` |

```handlebars
<a href="{{~#if isSummerCampaign~}}https://example.com/summer?cid={{~campaignId~}}
{{~else~}}https://example.com/winter?cid={{~campaignId~}}
{{~/if~}}">Shop</a>
```

---

## Sources

Iterable Support: [Handlebars Overview](https://support.iterable.com/hc/en-us/articles/35601631606036) · [Personalizing Templates with Handlebars](https://support.iterable.com/hc/en-us/articles/205480365) · [Conditional Logic Helpers](https://support.iterable.com/hc/en-us/articles/115003884806) · [Text Helpers](https://support.iterable.com/hc/en-us/articles/36488450881044) · [Math Helpers](https://support.iterable.com/hc/en-us/articles/36282964645396) · [Date and Time Helpers](https://support.iterable.com/hc/en-us/articles/36267178160788) · [Looping Over Objects and Arrays](https://support.iterable.com/hc/en-us/articles/36531450481300) · [Encoding and Hashing Helpers](https://support.iterable.com/hc/en-us/articles/209732326) · [Regular Expressions](https://support.iterable.com/hc/en-us/articles/211728403) · [Troubleshooting Handlebars Code](https://support.iterable.com/hc/en-us/articles/36530857619348)

SHA-256: 1cf0e1f8cf7e61087356c5d693e8211a3b786aaff53d60c6e81c39eaf74a2fee