# Handlebars Reference Airship uses the Handlebars language to support personalization of messaging content. # Handlebars basics > Airship uses the [Handlebars](https://handlebarsjs.com/) templating language to support personalization of messaging content. > **Note:** This is a reference for using Handlebars as implemented in Airship's features, not for the Handlebars project. We do not support all features described in handlebarsjs.com, only those included in this documentation. > **Tip:** Airship has built-in tools that simplify entering Handlebars expressions. See: [Dashboard tools for Handlebars](https://www.airship.com/docs/guides/personalization/simplifying-handlebars/). ## Syntax Handlebars syntax works by wrapping a variable in curly braces. Merge fields are the most basic Handlebars expression. Insert your merge field into content by wrapping the name of your field in curly braces, e.g., `{{firstname}}`. Airship replaces the merge field (and braces) with the data specified by the merge field at send time. > **Note:** Variables that start with a number require a special syntax, using either brackets `[]` or the `lookup` helper. For example, to use a variable named `42_answer`, use `{{ [42_answer] }}` or `{{lookup . "42_answer"}}`. ### Block expressions Block expressions are boolean-returning helpers that conditionally render blocks of content if their evaluation is true. These helpers use a `#` preceding the helper name and require a matching closing bracket of the same name with a `/` in front of it. See examples in [Logic/conditional statements](https://www.airship.com/docs/guides/personalization/handlebars/logic-helpers/). ## URL encoding In HTML for email, Message Center, In-App Automation, and landing pages, values in Handlebars statements are not automatically URL-encoded. For example, if an email link includes characters that are not URL-safe and you haven't used the `urlEncode` helper, the invalid link may be replaced with a default URL, such as `https://example.com`. When writing your own custom HTML or in any other situation where you might want to URL encode variables, you can use the `urlEncode` helper: `{{urlEncode var_name}}`. Image and "link" properties in the [Interactive editor](https://www.airship.com/docs/guides/messaging/editors/interactive/) and message actions expect URLs. For those fields, Airship automatically URL-encodes values in Handlebars expressions to ensure valid URLs. **When to use double braces `{{var}}`:** Use for variables that need URL encoding, such as user-generated content that might contain spaces or special characters. - Example: `https://www.example.com/user/{{user_name}}/{{cart_id}}` - If `user_name` is `John Smith`, this becomes `https://www.example.com/user/John%20Smith/123` **When to use triple braces `{{{var}}}`:** Use for variables that contain complete URLs or are already URL-safe to avoid double-encoding. - Example: `{{{redirect_url}}}` - If `redirect_url` is already `https://example.com/page%20one`, it remains unchanged **Avoiding double-encoding:** Be careful not to URL-encode values that are already encoded, as this creates invalid URLs. For example, `{{"my%20value"}}` becomes the double-encoded "my%2520value" instead of the correct "my%20value". **URL encoding push example** ```json { "audience": { "named_user": "one_user" }, "notification": { "android": { "alert": "Hi {{value_nospace}} go to https://www.example.com/{{urlEncode value_with_space}}!" }, "actions": { "open": { "type": "url", "content": "{{{value_full_url}}}" } } }, "device_types": [ "android" ] } ``` ## Default handler {#def} Use `$def` to set a default value for one or more merge field variables, so that your message will still make sense if the field is empty or doesn't exist. When using more than one variable, the handler will use the first variable value that isn't empty. When using a default, provide both: * The parameters you want to use in your message * The default value if your variables are empty In addition to a variable or value such as a string or a number, expressions that produce a value can also be used in the handler. For example, `($capitalize first_name)` will return the string value of `first_name` with the first letter capitalized. **Properties**: * `parameter` — The value or expression/variable name whose value will be used if it exists and isn't empty. * `value` — A string to be used as a fallback default if all preceding variables or expressions were empty or did not exist. | Format | Example | Output | | --- | --- | --- | | `{{$def parameter1 parameter2 parameter3... "value if keys are empty"}}` | `{{$def name "you"}}` | Welcome you! | ## Object and array notation {#dot-notation} The user data and merge fields you use to populate a template are often nested in objects or arrays — like when using [Custom Events](https://www.airship.com/docs/developer/rest-api/ua/operations/custom-events/#addcustomevents) to populate a template in an automation rule. Use standard dot notation to access properties in objects or an item/index in an array. For example, you could access a `productName` property in a `suggestedProduct` object using `suggestedProduct.productName`. Use the array index to access an item at a particular location in an array. For example, if your message includes an array of objects called `suggestedProducts` for each member of your audience, you can access the first suggested product with `suggestedProducts.[0]` (array index 0). > **Note:** Array indexes start at 0. If you want to reference a key called `image` belonging to the first suggested product in the array, you would use `suggestedProducts.[0].image`. ### Object and array notation for Create and Send For [Create and Send](https://www.airship.com/docs/reference/glossary/#create_and_send) CSVs, you can include complex arrays and objects in your CSV using object notation to represent object properties in the header. All values in CSV uploads, including numbers, are represented as strings and cannot be used with [math helpers](https://www.airship.com/docs/guides/personalization/handlebars/math-helpers/). When referencing an array index, **you must wrap the array index in brackets**. Your CSV should include headers for each item in the array and each property in the object that you want to reference in your message. For example, if you want to use an array of objects called `items` for each audience member in your CSV, your CSV will include `items.[#]` for each item in the array. If each object in the array had `name`, `image`, and `url` properties, you would add `items.[0].name, items.[0].image, items.[0].url` to your CSV, and reference additional objects in the array by incrementing the index (e.g. `items.[1]` and so on). > **Note:** Array indexes start at 0. If you wanted to personalize an email to members of your audience based on their addresses and the names of items they're interested in, you might format your CSV as follows: ```text ua_address,ua_commercial_opted_in,name,address.city,address.state,items.[0].name,items.[1].name someone@example.com,2018-04-01T18:45:30,Joe Someone,Portland,OR,Rubber Gloves,Bleach Alternative else@example.com,2018-04-21T16:13:01,Sir Else,Seattle,WA,Flashlight,Shovel ``` # Text helpers > Text helpers transform text content in Handlebars expressions. ## Limit string length (Truncate) {#truncate} `truncate` — Limits string length. **Properties** * `string` — The string, string variable, or expression that produces a string whose length you want to limit. * `max_length` — An integer representing the maximum string length. * `replacement_suffix` — An optional string you want to append to the end of the truncated string if, and only if, Airship truncates the string. | Format | Example | Output | |---|---|---| | `{{truncate [replacement_suffix]}}` | How did you like your`{{truncate product_name 5 "..."}}`? | How did you like your produ...? | > **Note:** The `max_length` includes the replacement suffix, so if you set a `max_length` of 8, and a `replacement_suffix` with 3 characters (e.g. `...`), Airship will only display 5 characters from a truncated string; the remaining 3 characters of any truncated string are consumed by the `replacement_suffix`. > > For example, if you have an attribute ID `name` with value `yourname`, `{{truncate name 7 "---"}}` would resolve to `your---`. Without the suffix, the same property would truncate to `yournam`. ## Contains `$contains` — Checks whether a string `target` contains string `searchFor`. **Properties** * `target` — The string or user-specific variable to search. * `searchFor` — The string to find within `target`. | Format | Example | Output | |---|---|---| | `{{#if ($contains target searchFor)}}...{{else}}...{{/if}}` | `{{#if ($contains "some string context" "str")}}true{{else}}false{{/if}}` | `true` | ## Replace `$replace` — Replaces all occurrences of `searchFor` with `replaceWith` in string `target`. **Properties** * `target` — The string or user-specific variable to be searched. * `searchFor` — A string within the `target` to be changed. * `replaceWith` — The intended change to `target` if `searchFor` returns true. * `limit` — Optional. Replaces only the first `X` occurrences. | Format | Example | Output | |---|---|---| | `{{$replace target searchFor replaceWith [limit=]}}` | `{{$replace "Some terrible jawn." "terrible" "cool"}}` | Some cool jawn. | ## Split `$split` — Splits a string `target` on a given string `delimiter`, returning an array of strings split on the characters in `delimiter`. You may also specify a `limit` to only split a string a maximum number of times. **Properties** * `target` — The string to be split. * `delimiter` — The characters on which to split. * `limit` — The maximum number of splits to perform. | Format | Example | Output | |---|---|---| | `{{$split target delimiter index}}` | `{{$split "a,b,c" ","}}`
`{{$split "a,b,c" "," 1}}` | `["a", "b", "c"]`
`["a", "b,c"]` | ## Join `$join` — Joins an array `target` using a given string `delimeter`, outputting a single string. **Properties** * `target` — The array to be joined. An error will be thrown if this parameter is any type other than an array. * `delimeter` — A string to insert between the elements in the `target` array. | Format | Example | Output | |---|---|---| | `{{$join target delimeter}}` | `{{$join ($array "red" "green" "blue") "-"}}`
`{{$join ($array 1 2 3) ", "}}` | `red-green-blue`
`1, 2, 3` | ## Trim `$trim` — Trims whitespace from both ends of a string. **Properties** * `target` — The string to be trimmed. | Format | Example | Output | |---|---|---| | `{{$trim target}}` | `{{$trim " abc "}}` | `abc` | ## Splice `$splice` — Inserts string `insertContent` into string target at index `index`. **Properties** * `target` — The string or user-specific variable to be searched. * `index` — The count from start of `target` to point of replacement. Note `index` lengths are 0-indexed. * `insertContent` — The string or content to be inserted at the `index` point. * `deleteCount` — Optional. Deletes `X` characters from `index`. | Format | Example | Output | |---|---|---| | `{{$splice target index insertContent deleteCount=X}}` | `{{$splice "xxxzzz" 3 "yyy"}}`
`{{$splice "xxxzzz" 1 "y" deleteCount=4}}` | xxxyyyzzz
xyz | ## Capitalize `$capitalize` — Capitalizes the first letter of a given string. **Properties** * `string` — The string or user-specific variable to capitalize. * `locale` — Optional. An IETF BCP 47 language tag. | Format | Example | Output | |---|---|---| | `{{$capitalize string locale=DEFAULT}}` | `{{$capitalize "word"}}` | Word | ## Uppercase `$uppercase ` — Converts a given string to uppercase. **Properties** * `string` — The string or user-specific variable to uppercase. * `locale` — Optional. An IETF BCP 47 language tag. | Format | Example | Output | |---|---|---| | `{{$uppercase string locale=DEFAULT}}` | `{{$uppercase "word"}}` | WORD | ## Lowercase `$lowercase` — Converts a given string to lowercase. **Properties** * `string` — The string or user-specific variable to lowercase. * `locale` — Optional. An IETF BCP 47 language tag. | Format | Example | Output | |---|---|---| | `{{$lowercase string locale=DEFAULT}}` | `{{$lowercase "Word"}}` | word | ## Format number `$numberFormat` — Converts a number to a given `locale` and `precision`. **Properties** * `number` — The number to be formatted. * `locale` — An IETF BCP 47 language tag, e.g., `en-US`. * `precision` — Optional. An integer specifying the maximum number of decimal places that should be shown. | Format | Example | Output | |---|---|---| | `{{$numberFormat number locale=locale precision=precision}}` | `{{$numberFormat 100000 locale="en-US" }}`
`{{$numberFormat 100000.012 locale="en-US" precision=2}}`
`{{$numberFormat 1000 locale="da-DK" precision=2}}` | 100,000
100,000.01
1.000,00 | ## Format currency `$currencyformat` — Converts a number into a currency representation for the given `locale` and `precision`. **Properties** * `number` — The number to be formatted. * `locale` — An IETF BCP 47 language tag, e.g., `en-US`. * `precision` — Optional. An integer specifying the maximum number of decimal places that should be shown. | Format | Example | Output | |---|---|---| | `{{$currencyFormat number locale=locale precision=precision}}` | `{{$currencyFormat 100.129 locale="en-US"}}`
`{{$currencyFormat 120.999 locale="da-DK"}}` | $ 100.13
€ 120,99 | # Math helpers > Math helpers transform number or integer content in Handlebars expressions. > **Important:** * These helpers work only with numbers and properties that resolve to numbers or integers. All values in CSV uploads, including numbers, are represented as strings and cannot be used with math helpers. This is especially relevant when using [Bulk sending](https://www.airship.com/docs/guides/audience/segmentation/bulk-sending/) methods. > > * You cannot nest math operations or use other helpers inside a math helper. For example, you cannot use `$def` to set a default value for a property in a math helper. > > * Take care to use properties that you know exist and have number/integer values, so that your message makes sense to your audience. ### Add {#add-helper} `add` — Adds `number`. Add helpers take an unlimited number of arguments. For example, `{{add 5 2 2}}` resolves to `9`. **Properties** * `number` — Variable that contains a number value, no limit on values that can be added for a sum total. You can add as many numbers as fits your message. | Format | Example | Output | |---|---|---| | `{{add number1 number2...}}` | You can have `{{add slices_per_person left_overs}}` slices of pizza! | You can have 3 slices of pizza! | ### Subtract `sub` — Subtracts `number`. Subtract helpers take an unlimited number of arguments. For example, `{{sub 5 2 2}}` resolves to `1`. You can subtract as many numbers as fits your message. **Properties** * `number` — Variable that contains a number value or an integer. | Format | Example | Output | |---|---|---| | `{{sub number1 number2...}}` | You can have `{{sub slices_per_person left_overs}}` more slice of pizza! | You can have 1 more slice of pizza! | ### Multiply `mul` — Multiplies `number`. Multiplication helpers take an unlimited number of arguments. For example, `{{mul 4 2 2}}` resolves to `16`. **Properties** * `number` — Variable that contains a number value or an integer. | Format | Example | Output | |---|---|---| | `{{mul number1 number2...}}` | You need `{{mul slices_per_person people}}` total pizza slices for the party. | You need 36 total pizza slices for the party. | ### Divide `div` — Divides `number`. Division helpers take an unlimited number of arguments. For example, `{{div 4 2 2}}` resolves to `1`. **Properties** * `number` — Variable that contains a number value or an integer. | Format | Example | Output | |---|---|---| | `{{div number1 number2...}}` | Pizza partiers can each have `{{div total_slices people}} ` slices. | Pizza partiers can each have 3 slices. | ### Modulo `mod` — Returns the remainder when dividing two `number`. This handler takes exactly two arguments. **Properties** * `number` — Variable that contains a number value or an integer. | Format | Example | Output | |---|---|---| | `{{mod number1 number2}}` | Pizza slices up for grabs: `{{mod total_slices slices_per_person}}` | Pizza slices up for grabs: 2 | ### Rounding numbers `round` — Rounds `number` to the nearest integer. You can round to the nearest decimal place by adding second argument determining the decimal place you want to round to. **Properties** * `number` — Variable that contains a number value or an integer. * `place` — Optional. Numerical value to round to a decimal place. **Round to the nearest integer** | Format | Example | Output | |---|---|---| | `{{round number}}` | Pi is pretty close to `{{round pi}}` | Pi is pretty close to 3 | **Round to a decimal place** | Format | Example | Output | |---|---|---| | `{{mod number place}}` | Pi is pretty close to `{{round pi 2}}` | Pi is pretty close to 3.14 | ### Ceiling `$ceil` — Rounds up to the nearest integer. **Properties** * `number` — The number to be rounded. | Format | Example | Output | |---|---|---| | `{{$ceil number}}` | `{{$ceil 3.001}}` | 4 | ### Floor `$floor` — Rounds down to the nearest integer. **Properties** * `number` — The number to be rounded. | Format | Example | Output | |---|---|---| | `{{$floor number}}` | `{{$floor 3.999}}` | 3 | # Date and time helpers > Date and time helpers transform content in Handlebars expressions. ## Formatting dates and times You can format dates and times using helpers `dateFormat`, `timeFormat`, and `datetimeformat`. Each helper takes the arguments [`date-time`](#date-time), [`locale`](#locale), [`format`](#format), and [`pattern`](#pattern). Following the helper, you must include a `date-time` and `locale`. For example, `{{dateFormat "1983-01-31T12:36:35" locale="en-US"}}`. The `format` and `pattern` arguments are optional. Examples of each helper: | Helper | Rendered as | Example | Output | | --- | --- | --- | --- | | dateFormat | Date | `{{dateFormat "1983-01-31T12:36:35" locale="en-US"}}` | Jan 31, 1983 | | timeFormat | Time | `{{timeFormat "1983-01-31T12:36:35" locale="en-US"}}` | 12:36:35 PM | | datetimeFormat | Date and time | `{{datetimeFormat "1983-01-31T12:36:35" locale="en-US"}}` | Jan 31, 1983, 12:36:35 PM | You can also provide date-times using Date [Attributes](https://www.airship.com/docs/reference/glossary/#attributes) instead of a date-time string. When using Attributes, do not enclose them in quotes. These examples show how the `locale`, `format`, and `pattern` arguments can transform a date-time, using a Date [Attribute](https://www.airship.com/docs/reference/glossary/#attributes) `birthdate` with value January 31, 1983, for the date-time: | Argument | Example | Output | | --- | --- | --- | | Format using the `locale` argument only. | `{{dateFormat birthdate locale="en-US"}}` | Jan 31, 1983 | | Add the `format` argument to specify a shorter, numeric-only output. | `{{dateFormat birthdate locale="en-US" format="SHORT"}}` | 1/31/1983 | | Change the `format` value to `FULL` to expand "Jan" to "January" and include the day of the week. | `{{dateFormat birthdate locale="en-US" format="FULL"}}` | Monday, January 31, 1983 | | Use the `pattern` argument to specify custom formatting. | `{{dateFormat birthdate locale="en-US" pattern="d MMMM yyyy"}}` | 31 January 1983 | | Change the `locale` value to change the language. The example here changes the output French. | `{{dateFormat birthdate locale="fr-FR" pattern="d MMMM yyyy"}}` | 31 janvier 1983 | ### Date-time An [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601)-formatted string — Required. In general, we recommend that you use uniform date-time values since they work for all three helpers, but the actual date-time string requirements vary per helper. You can add `[Offset]` to specify the time zone offset from UTC. Use `Z` for UTC, or one of the following formats `±00:00`, `±0000`, `±00`. For example, `-08:00`, `-0800`, or `-08` for PST. Date-time format per helper, with optional parts of the string in brackets: | Helper | Optional data | Date-time format | | --- | --- | --- | | dateFormat | Time | `yyyy-MM-dd[' ']['T']HH:mm[:ss[.SSS][Offset]]` | | timeFormat | Date | `[yyyy-MM-dd[' ']['T']]HH:mm[:ss[.SSS]][Offset]` | | datetimeFormat | n/a | `yyyy-MM-dd[' ']['T']HH:mm[:ss[.SSS]][Offset]` | ### Locale `locale` — Required. Date and time helper argument that renders the date and time according to the user's 2-letter language and country codes, joined by a dash. Format example: `locale="en-US"`. The language is an [ISO 639](https://en.wikipedia.org/wiki/ISO_639) code. The country is an [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code. | Example | Output | | --- | --- | | `{{dateFormat "1983-01-31T12:36:35" locale="en-US"}}` | Jan 31, 1983 | | `{{dateFormat "1983-01-31T12:36:35" locale="fr-FR"}}` | 31 janv. 1983 | | `{{dateFormat "1983-01-31T12:36:35" locale="de-DE"}}` | 31.01.1983 | | `{{timeFormat "1983-01-31T12:36:35" locale="en-US"}}` | 12:36:35 PM | | `{{timeFormat "1983-01-31T12:36:35" locale="fr-FR"}}` | 12:36:35 | | `{{timeFormat "1983-01-31T12:36:35" locale="de-DE"}}` | 12:36:35 | | `{{datetimeFormat "1983-01-31T12:36:35" locale="en-US"}}` | Jan 31, 1983, 12:36:35 PM | | `{{datetimeFormat "1983-01-31T12:36:35" locale="fr-FR"}}` | 31 janv. 1983 à 12:36:35 | | `{{datetimeFormat "1983-01-31T12:36:35" locale="de-DE"}}` | 31.01.1983, 12:36:35 | Locale values can also be provided using Date [Attributes](https://www.airship.com/docs/reference/glossary/#attributes) instead of an actual date-time string. > **Tip:** The Airship SDK gathers `ua_language` and `ua_country` and stores them as Default [Attributes](https://www.airship.com/docs/reference/glossary/#attributes) for your app audience. You can use the Attributes as `locale` values. ### Format `format` — Optional. Date and time helper argument that determines how to display the date-time value. One of `FULL`, `LONG`, `MEDIUM`, or `SHORT`. Format example: `format="SHORT"`. `FULL` and `LONG` formats include time zone information. If you don't set a time zone value in your date-time string, Airship assumes `Z` (UTC/GMT). `format` cannot be used in combination with `pattern`. The following tables show examples and output of `format` for each date and time helper. `dateFormat` examples: | Format | Example | Output | | --- | --- | --- | | SHORT | `{{dateFormat "1983-01-31T12:36:35" locale="en-US" format="SHORT"}}` | 1/31/1983 | | MEDIUM | `{{dateFormat "1983-01-31T12:36:35" locale="en-US" format="MEDIUM"}}` | Jan 31, 1983 | | LONG | `{{dateFormat "1983-01-31T12:36:35" locale="en-US" format="LONG"}}` | January 31, 1983 | | FULL | `{{dateFormat "1983-01-31T12:36:35" locale="en-US" format="FULL"}}` | Tuesday, January 31, 1983 | `timeFormat` examples: | Format | Example | Output | | --- | --- | --- | | SHORT | `{{timeFormat "1983-01-31T12:36:35" locale="en-US" format="SHORT"}}` | 12:36 PM | | MEDIUM | `{{timeFormat "1983-01-31T12:36:35" locale="en-US" format="MEDIUM"}}` | 12:36:35 PM | | LONG | `{{timeFormat "1983-01-31T12:36:35" locale="en-US" format="LONG"}}` | 12:36:35 PM Z | | FULL | `{{timeFormat "1983-01-31T12:36:35" locale="en-US" format="FULL"}}` | 12:36:35 PM Z | `datetimeFormat` examples: | Format | Example | Output | | --- | --- | --- | | SHORT | `{{datetimeFormat "1983-01-31T12:36:35" locale="en-US" format="SHORT"}}` | 1/31/1983 12:36 PM | | MEDIUM | `{{datetimeFormat "1983-01-31T12:36:35" locale="en-US" format="MEDIUM"}}` | Jan 31, 1983 12:36:35 PM | | LONG | `{{datetimeFormat "1983-01-31T12:36:35" locale="en-US" format="LONG"}}` | January 31, 1983 12:36:35 PM Z | | FULL | `{{datetimeFormat "1983-01-31T12:36:35" locale="en-US" format="FULL"}}` | Tuesday, January 31, 1983 12:36:35 PM Z | ### Pattern `pattern` — Optional. Date and time helper argument that specifies a custom formatting pattern. Format example: `pattern="MMM d, yyyy"`. Only the parts that are relevant to the type of formatting are allowed. For example, date parts are not allowed within `timeFormat`. Cannot be used in combination with `format`. The most common `pattern` specifiers: | Specifier | Output | Example | |-----------|----------------------------|-------------------------------------| | y | Year | `yyyy` for `2023`, `yy` for `23` | | M | Month | `M` for `4`, `MMMM` for `April` | | d | Day of Month | `d` for `5`, `dd` for `05` | | e | Day of Week | `e` for `2`, `eeee` for `Wednesday` | | a | AM/PM | `a` for `PM` | | h | Hour (1-12) | `hh` for `12` | | K | Hour (0-11) | `KK` for `11` | | k | Hour (1-24) | `kk` for `12` | | H | Hour (0-23) | `HH` for `11` | | m | Minute | `mm` for `30` | | s | Second | `ss` for `55` | | V | Time Zone ID | `VV` for `America/Los_Angeles` | | z | Time Zone Name | `z` for `PDT` | | O | Localized Zone Offset | `O` for `GMT+8` | | X | Offset 'Z' for zero | `X` for `Z`, `XXX` for `-07:00` | | Z | Offset | `Z` for `-0700` | The above specifiers adhere to the following rules: 1. When specifying numbers that may include a leading zero, a single character will omit the leading zero and double characters will include the leading zero. For example, for the fourth hour: * `h` will output `4` * `hh` will output `04` 1. When specifying text output, 1 to 3 characters specify shortened forms, four characters specifies the full form, five characters specifies the narrow form. For example, for the month of April with `en-US` locale: * `M` will output `4` * `MM` will output `04` * `MMM` will output `Apr` * `MMMM` will output `April` * `MMMMM` will output `A` 1. Literal values may be specified in square brackets. For example, `['T']` will yield `T`. Additional formatting options are available and follow the [Java `DateTimeFormatter` rules](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html). Example use and output of `pattern` for each date and time helper: | Helper | Example | Output | | --- | --- | --- | | dateFormat | `{{dateFormat "2023-03-29T13:30:10" locale="en-US" pattern="MMM d, yyyy"}}` | Mar 29, 2023 | | dateFormat | `{{dateFormat "2023-03-29T13:30:10" locale="de-DE" pattern="d MMMM yyyy"}}` | 29 März 2023 | | timeFormat | `{{timeFormat "13:30:10" locale="en-US" pattern="h:mma"}}` | 1:30PM | | datetimeFormat | `{{datetimeFormat "2023-03-29T13:30:10" locale="en-US" pattern="yyyy-MM-dd['T']HH:mm:ss"}}` | 2023-03-29T13:30:10 | ## Determining today, yesterday, or tomorrow Return `true` or `false` based on whether a given date is today, yesterday, or tomorrow relative to the current time in UTC: * `{{$isToday ""}}` * `{{$isYesterday ""}}` * `{{$isTomorrow ""}}` Dates must be formatted as `yyyy-mm-dd` or as a [date-time](#date-time). These helpers are generally used within an evaluation only rather than displaying the result. You can combine them with conditional operators to return true or false in a helper block. You can also use them with a preceding `#` and a closing `/` to create a separate block: ```text {{#if ($isToday birthdate)}} Happy birthday, {{name}} {{/if}} ``` ```text {{#$isToday birthdate}} Happy birthday, {{name}} {{/$isToday}} ``` ## Getting date and time Return the current date, or date and time, in UTC: * `{{$now}}` — Returns the current date and time in UTC in [datetimeFormat](#formatting-dates-and-times) (`yyyy-MM-ddTHH:mm:ssZ`). * `{{$today}}` — Returns the current date in [dateFormat](#formatting-dates-and-times) (`yyyy-MM-dd`). Example use in a purchase order: ```text Purchase Date: {{$today}} // Using `timeFormat` to remove the date to get only the time: Your order was received at {{timeFormat ($now) format="SHORT" locale="en-US"}}. ``` ## Calculating date and time Return an age, future date, or time between dates: | Helper and format | Description | | --- | --- | | `{{$age ""}}` | Returns an age as of today for a given date. | | `{{$addTo "" timeUnit=""}}` | Returns a future date for a given date by adding an amount of time in `days`, `weeks`, `months`, or `years` to a given date-time. `timeUnit` is optional and defaults to days when omitted. Time units are not case-sensitive. | | `{{$daysBetween "" ""}}` | Returns the number of days between two dates. | | `{{$weekBetween "" ""}}` | Returns the number of weeks between two dates. | | `{{$timeBetween "" "" timeUnit="}}` | Returns the number of `days`, `weeks`, `months`, or `years` between two dates. `timeUnit` is optional and defaults to days when omitted. Time units are not case-sensitive. | Dates must be formatted as `yyyy-mm-dd` or as a [date-time](#date-time). The following examples assume today's date is March 25, 2024, and some also use the [`$today`](#getting-date-and-time) and [`pattern`](#pattern) helpers: | Example | Output | | --- | --- | | `{{$age "1970-06-01"}}` | 53 | | `{{dateFormat ($addTo ($today) 7 timeUnit="days") locale="en-US"}}` | Apr 1, 2024 | | `{{dateFormat ($addTo ($today) 7 timeUnit="days") locale="en-US" pattern="dd-MM-yy"}}` | 01-04-24 | | `{{$daysBetween "2024-12-19" "2025-12-19"}}` | 365 | | `{{$weeksBetween "2024-12-19" "2025-12-19"}}` | 52 | | `{{$timeBetween "2024-12-19" "2025-12-19"}}` | 365 | | `{{$timeBetween "2024-12-19" "2025-12-19" timeUnit="days"}}` | 365 | | `{{$timeBetween "2024-12-19" "2025-12-19" timeUnit="weeks"}}` | 52 | | `{{$timeBetween "2024-12-19" "2025-12-19" timeUnit="months"}}` | 12 | | `{{$timeBetween "2024-12-19" "2025-12-19" timeUnit="years"}}` | 1 | For `$daysBetween`, `$weekBetween`, and `$timeBetween`: * If the second date is earlier than the first date, the result will be a negative number. * The calculation returns a whole number, representing the number of complete time units between the two dates. For example, if the time unit is `days` and there are 30 hours between the two dates, then the output will be "1". Example usage in messages: | Example | Output | | --- | --- | | `Your membership expires in three days, on {{dateFormat ($addTo ($today) 3 timeUnit="days") format="SHORT" locale="en-US"}}.` | Your membership expires in three days, on 3/28/24. | | `There are {{$daysBetween ($today) "2021-12-25"}} days left until Christmas!` | There are -821 days left until Christmas! | | `There are {{$daysBetween ($today) "2035-12-25"}} days left until Christmas 2035!` | There are 4292 days left until Christmas 2035! | | `There are {{$timeBetween "2024-01-29T12:36:35" "2024-01-31T12:36:35" springSaleEnds}} days left in our Spring Sale!` | There are 2 days left in our Spring Sale! | ## Transforming a date to another time zone Transform a date to another time zone using `{{$toTimezone "" ""}}`. Dates must be formatted as `yyyy-mm-dd` or as a [date-time](#date-time). The `timezone_id` is the text identifier for the time zone. For example, `America/Los_Angeles`. See the [TZ column](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) in the list of Time Zone abbreviations. The first example in each pair uses standard formatting, and the second adds the [`datetimeFormat` helper](#formatting-dates-and-times) and [`format` argument](#format) to give a more localized time: | Example | Output | | --- | --- | | `{{$toTimezone "2023-04-05T15:24:40Z" "America/Los_Angeles"}}` | 2023-04-05T08:24:40-07:00[America/Los_Angeles] | | `{{datetimeFormat ($toTimezone "2023-04-05T15:24:40Z" "America/Los_Angeles") locale="en-US" format="LONG"}}` | April 5, 2023 at 8:24:40 AM PDT | | `{{$toTimezone "2023-04-05T15:24:40Z" "America/Jamaica"}}` | 2023-04-05T10:24:40-05:00[America/Jamaica] | | `{{datetimeFormat ($toTimezone "2023-04-05T15:24:40Z" "America/Jamaica") locale="en-US" format="LONG"}}` | April 5, 2023 at 10:24:40 AM EST | # Logic/conditional statements > Logic helpers allow you to place conditional requirements in Handlebars expressions. Logic statements can use one or more of these properties to evaluate a condition: * `value` — A literal string or number (e.g. "Hello" or 3) * `attribute` — The string ID/Name of a user-specific data * `expression` — A helper that can use other helpers, attributes, and values to return a value ## If/Else `if`, `else`, `else if` — If/else statements can conditionally render blocks of content. *If* and *else if* statements render content when the condition is true, and *else* renders content when all previous conditions in a [block expression](https://www.airship.com/docs/guides/personalization/handlebars/basics/#block-expressions) are false. Use `if` explicitly to test that a variable exists, is not empty, or is not 0. Otherwise, any condition that evaluates a variable acts as an implicit *if* statement. For example, you must explicitly declare `if` when testing whether or not the `fav_movie` variable exists, is not empty, and is not 0. ```text {{#if fav_movie}} Your favorite movie is {{fav_movie}}. {{else}} Maybe you'd enjoy a book instead. {{/if}} ``` When testing the value of the `fav_movie` variable, you can use other logic helpers such as [Equal](#equal) and combine them with else statements. ```text {{#eq fav_movie "Indiana Jones"}}Did you like The Last Crusade or Temple of Doom? {{else eq fav_movie "Star Wars"}}Are you into the OG trilogy or are you a prequel person? {{else}}What's your favorite book?{{/eq}} ``` You can chain multiple `else` statements — similar to *else if* statements in other languages — to test a series of variables. Generally, the last `else` statement contains no parameters and determines what happens if all previous conditions fail. ## And/Or `and`, `or` — These operators help you string together complex conditions to show the right content to the right members of your audience. > **Note:** `and` renders a block of content if **all** conditions are true. > > `or` renders a block of content if **any** of the conditions are true. | Format | Example | Output | |---|---|---| | `{{#and expression expression}}` | `{{#and (eq genre "action") (eq era "80s") }}
You will definitely like Indiana Jones!
{{/and}}` | If **all** conditions are true, "You will definitely like Indiana Jones!" will display | | `{{#or expression expression}}` | `{{#or (eq genre "action") (eq era "80s") }}
You might be interested in Indiana Jones.
{{/or}}` | If **any** conditions are true, "You might be interested in Indiana Jones. will display | ## Equal `eq` — Equality operators let you test the value of a merge field or the result of an expression — whether it is equal to, or not equal to, specific value. > **Tip:** Equality operators work well with both numeric or string (text) values. But cannot compare between the two value types. In this example, the message is different if the `category` merge field contains the value `Accessories`: ```text {{#eq category "Accessories"}} Enjoy up to $25 off! {{else}} Get up to $75 off! {{/eq}} ``` | Format | Example | Output | |---|---|---| | `{{#eq ## Equal `eq` — Equality operators let you test the value of a merge field or the result of an expression — whether it is equal to, or not equal to, specific value. > **Tip:** Equality operators work well with both numeric or string (text) values. But cannot compare between the two value types. In this example, the message is different if the `category` merge field contains the value `Accessories`: ```text {{#eq category "Accessories"}} Enjoy up to $25 off! {{else}} Get up to $75 off! {{/eq}} ``` | Format | Example | Output | |---|---|---| | `{{#eq expression expression}}` | `{{#eq quantity 2}}You have 2 items in your cart{{/eq}}` | Displays text if `quantity` is equal to `2` | ## Not equal `neq` — Renders content when a merge field or the result of an expression is *not* equal to a value. | Format | Example | Output | |---|---|---| | `{{#neq expression expression}}` | `{{#neq movie "Looper"}}You answered incorrectly. The correct answer is Looper{{/neq}}` | Display text if `movie` does not equal `Looper` | ## Not `not` — Renders content if a merge field or the result of an expression does not exist, is empty, is false, or is 0. This is a direct way to test for the availability of a merge field. | Format | Example | Output | |---|---|---| | `{{#not expression}}` | `{{#not watch_queue}}Your queue is empty, add a show to watch.{{/not}}` | If `watch_queue` is empty or 0, "Your queue is empty, add a show to watch." will display | ## Unless `unless` — Renders content in a template if a condition is false — like a reverse `if` condition. You might want to use `unless` when you only really need the `else` statement for a condition. | Format | Example | Output | |---|---|---| | `{{#unless expression}}` | `{{#unless dislikes_indiana_jones}}You'll probably like Indiana Jones{{/unless}}` | Airship will render message content if the condition `dislikes_indiana_jones` is empty or false. | ## Greater than and Less than `gt`, `lt`, `gte`, `lte` — Use the following operators to show content if the value of a variable — or the length of an array — is greater than, less than, greater than or equals, or less than or equals a particular value. > **Tip:** These operators are for numerical values only. | Format | Example | Output | |---|---|---| | `{{#gt expression}}` | `{{#gt quantity 2}}You have more than 2 items in your cart{{/gt}}` | Displays text if `quantity` is greater than `2` | | `{{#lt expression}}` | `{{#lt quantity 2}}You have less than 2 items in your cart{{/lt}}` | Displays text if `quantity` is less than `2` | | `{{#gte expression}}` | `{{#gte quantity 2}}You have enough items in your cart to unlock our discount{{/gte}}` | Displays text if `quantity` is greater than or equal to `2` | | `{{#lte expression}}` | `{{#lte quantity 2}}You don't have enough items in your cart to unlock our discount{{/lte}}` | Displays text if `quantity` is less than or equal to `2` | > **Note:** Array lengths are 0-indexed. For example, if an array has 3 entries, `{{#gte field.length 2}}` will be true. # Looping through objects and arrays > Loop through and repeat content from an array or object in your personalized message content. You can use the `#each` operator to loop through and repeat content from an array or object. For example, you can show a user all the items in their shopping cart, including quantity, description, and price. Specify the key of the array or object that you want to start your loop from using `{{#each }}`. You can access properties within the loop with the [Handlebars](https://www.airship.com/docs/reference/glossary/#handlebars) expressions on this page. > **Note:** You cannot loop using Handlebars without an array or object data to loop through. ## Setting a loop limit {#each} When you use `#each`, you can limit the number of loops to perform, making sure that your message is a reasonable length. Set a limit using `#each (limit )`. ```text We think you might like: {{#each (limit array_of_objects 2) as |obj|}} {{obj.name}}: {{obj.desc}} {{/each}} ``` ## Setting context using #with {#with} Use `{{#with}}` to set the context within your template. This can help you access nested keys without having to repeat the parent object's path. For example, if your audience's shipping information is nested in an object called `shippingAddress`, your code might look like this: ```text {{shippingAddress.street}} {{shippingAddress.state}}, {{shippingAddress.zipCode}} ``` You then might use the `with` helper to limit context and simplify your template like this ```text We'll ship your package to: {{#with shippingAddress}} {{street}} {{state}}, {{zipCode}} {{/with}} ``` ## Creating and concatenating arrays When constructing new arrays or concatenating existing arrays, the following operators are available: * `{{$array }}` — Creates a new array from the given items. Any number of items may be passed as parameters, e.g., `{{$array "a" "b" "c"}}` creates a new array containing `["a", "b", "c"]`. * `{{$concat }}` — Creates a new array from a given list of arrays. The [`with`](#with) helper can be useful here to assign these created objects to new variables: ```text {{#with ($array "a" "b" "c") as |arr|}} {{#each arr}}{{.}}{{#unless @last}}, {{/unless}}{{/each}} {{/with}} ``` ...which will yield `a, b, c`. ## Checking for presence The `$contains` operator can be used to check if a value is present in an array. * `{{$contains }}` — Returns a boolean value representing if `search_item` exists in `array`. ```text {{#if ($contains favorite_movies "Brazil")}} You must be a Terry Gilliam Fan. {{/if}} ``` ## Working with key/value objects The `$merge` and `$pick` operators can help you construct new key/value objects from existing ones. * `{{$pick }}` — Constructs a new object from `keys` picked from `object`. If a key is not found in the object, it will be omitted. You may specify as many keys as you like, e.g., `{{$pick obj "key1" "key2"}}`. * `{{$merge }}` — Constructs a new object by merging the given objects. They are merged only on their top-level keys, and the right-most object wins if the same key is present in multiple objects. ```text {{#with ($pick ($merge obj1 obj2) "key1" "key2") as |newObj|}} {{#each newObj}} * {{@key}}: {{.}} {{/each}} {{/with}} ``` ## Filtering empty values You can use the `$filterEmpty` operator to filter any empty values from an array or key/value object. When filtering a key/value object, values are evaluated for emptiness: ```text {{#with ($filterEmpty ($array "a" "b" "" "c")) as |arr|}} {{#each arr}}{{.}}{{#unless @last}}, {{/unless}}{{/each}} {{/with}} ``` ...which will yield `a, b, c`. ## Getting the number of items in objects and arrays Using the `length` property on an array, or the `size` property on a map or object, you can get the number of items or number of keys, respectively. ```text {{#gt array.length 3}} Array length was greater than 3! {{/gt}} {{#gt object.size 3}} Object had more than 3 keys! {{/gt}} ``` ## Referencing properties in objects and arrays Because your loop reference begins from the object or array specified by `#each`, you don't need to provide the full path of the property that you want to reference, just the path to the key within the current iteration of the loop. For example, if looping through an array of objects called `suggestedProducts`, you could specify the keys within each object — `qty` and `productName`. ```text {{#each suggestedProducts}} {{qty}}x {{productName}} {{/each}} ``` You can also reference properties of the array itself within the context of the loop. * `{{.}}` — Accesses the current item in the array, generally for simple arrays of strings or integers. * `{{@index}}` — References the current array index in the loop. * `{{@key}}` — Provides the key name or index location of the current loop iteration. * `{{@root}}` — Accesses the root element properties while you iterate in the context of any nested or child elements. * `{{@first}}` — Returns true for the first element in the loop and can be used with If/Else statements. For example, `{{#each array}} {{#if @first}} Hello! {{/if}} {{/each}}` will include "Hello!" in front of the first object in your array. * `{{@last}}` — Returns true for the last element in the loop and can be used with If/Else statements. For example, `{{#each array}} {{#if @last}} Goodbye! {{/if}} {{/each}}` will include "Goodbye!" in front of the last object in your array. * `{{this}}` — Limits the context to the current object iteration of the loop. Use dot notation to reference a key in the object, i.e., `{{this.key}}`. In general, you don't need to use `this` unless you have a duplicate key outside the loop and the key in your loop is optional. In such a case, `this` prevents the template from finding and using a duplicate key outside the current iteration of the loop if the key in the loop is missing. **Reference names in a simple array:** ```text Everybody coming to the pizza party: {{#each pizza_partiers}} {{@index}}: {{.}} {{/each}} ``` **Reference items in a user's shopping cart as an array of objects:** ```text Are you still interested in the following items? {{#each cart}} * {{qty}}x {{product}} for {{price}} {{/each}} ``` ## Using a feed An *External Data Feed* is a connection to an external API. When you send a message, Airship uses a response from that API to personalize messages. See: [External data feeds](https://www.airship.com/docs/guides/personalization/sources/external-data-feeds/). You reference an external feed as a block, and you can use properties from your feed within the block — `{{#feed "feed_name" var_in_url="value" as |alias|}}`. For example, imagine you have a feed at `https://www.example.com/[[region]]/featured-products` that returns an array of products that you want to display to your audience. You may want to set the region at send time (rather than using the default value from your feed), and you may want to provide an alias so that your feed properties don't collide with attributes or custom event properties. Your message text and Handlebars might look something like this: ```text {{#feed "featured_products" region="us" as |result|}} {{#each (limit result.products 2) as |product|}} {{product.name}}: {{product.price}} https://cool-store.tld/us/products/{{urlEncode product.slug}}/ {{else}} Check back later for deals! {{/each}} {{else}} Couldn't fetch featured products! {{/feed}} ``` > **Note:** While the feed block grants access to variables from the feed response, you can still reference variables (or [merge fields](https://www.airship.com/docs/reference/glossary/#merge_field)) that aren't a part of the feed — attributes, custom event properties, etc. If this is something you may do, set the feed namespace using `as |alias|` to differentiate between properties from the feed and other personalization variables. # Encoding and hashing helpers > Encoding, hashing, and secret hashing functions transform content in Handlebars expressions. The helpers described in this document can hash and otherwise transform strings using standard hashing functions, such as MD5 and SHA256. In many of these cases, you will need to transform the output to various encodings, such as Base64 or Hex. All functions required to perform these transforms are described in this document. In all cases where strings can be provided, they are converted to UTF-8 bytes. ## Encoding The following helpers can be used to change the encoding of a variable. They are commonly used when transforming a byte array, e.g., as output from a hash function, into readable characters. ### Base64 encode {#base64-encode} `$base64Encode` — Given a particular string or byte array, encode to Base64. **Properties** * `content` — The string or byte array to encode. * `pad` — Optional. A boolean specifying if the output should be padded. Default is `true` when unspecified. | Format | Example | Output | |---|---|---| | `{{$base64Encode content pad=true}}` | `{{$base64Encode "wut"}}` | `d3V0` | ### Base64 decode {#base64-decode} `$base64Decode` — Given a Base64-encoded string, decode to an array of bytes. **Properties** * `content` — The Base64 encoded string to decode. | Format | Example | Output | |---|---|---| | `{{$base64Decode content}}` | `{{$utf8Decode ($base64Decode "d3V0")}}` | `wut` | ### UTF-8 decode {#utf-8-decode} `$utf8Decode` — Given an array of bytes, decode as UTF-8 text. **Properties** * `content` — The bytes to decode. For an example, see [Base64 decode](#base64-decode) above. ### UTF-8 encode {#utf-8-encode} `$utf8Encode` — Given a string, encode as a UTF-8 byte array. **Properties** * `content` — The string to encode. This function is provided as an inverse of [UTF-8 decode](#utf-8-decode). ### Hex encode {#hex-encode} `$hexEncode` — Given a string or bytes, encode to a hex representation string. **Properties** * `content` — The string or bytes to encode. | Format | Example | Output | |---|---|---| | `{{$hexEncode content}}` | `{{$hexEncode "wut"}}` | `777574` | ## Hashing Hashing helpers are provided for the following algorithms: * MD5: `$md5` * SHA1: `$sha1` * SHA256: `$sha256` * SHA512: `$sha512` All have the same usage but use their specified algorithm to perform the hash. These functions return byte arrays, which should then be [Hex](#hex-encode) or [Base64](#base64-encode) encoded. **Properties** * `content` — The string or byte array to hash. | Format | Example | Output | |---|---|---| | `{{$ content}}` | `{{$hexEncode ($md5 "wut")}}` | `c268120ce3918b1264fe2c05143b5c4b` | ## HMAC [HMAC](https://en.wikipedia.org/wiki/HMAC) hash functions are provided for the following common algorithms: * MD5: `$hmacMd5` * SHA1: `$hmacSha1` * SHA256: `$hmacSha256` * SHA512: `$hmacSha512` All have the same usage but use their specified algorithm to perform the hash. These functions return byte arrays, which should then be [Hex](#hex-encode) or [Base64](#base64-encode) encoded. **Properties** * `content` — The string or byte array to hash. * `secret` — The secret key used to sign the hash. | Format | Example | Output | |---|---|---| | `{{$hmac content secret=secret}}` | `{{$hexEncode ($hmacSha1 "value" secret="key")}}` | `57443a4c052350a44638835d64fd66822f813319` | ## Encryption Symmetrical AES Encryption is provided by the `$aes` helper using AES in CBC mode with PKC55 padding. Output is Base64 encoded automatically. **Properties** * `content` - The string or byte array to encrypt * `secret` - A 256-bit (32 character) string used to encrypt the content. * `iv` - A 128-bit (16 character) initialization vector. To avoid compromising security, the initialization vector should be unique for each encryption. | Format | Example | Output | |---|---|---| | `{{$aes content secret=secret iv=iv}}` | `{{$aes 'This string is encrypted' secret='7C45C93E-5216-48ED-88D4-6EB7C413' iv='myinitialization' }}` | `bwNBr+/yfYYPoE2aq/zqfzp6kxctfFC+MCgacwwkJSQ=` | Below is a reference implementation for decryption of the above example using NodeJS. ```javascript const crypto = require('node:crypto'); const decipherIV = crypto.createDecipheriv( 'AES-256-CBC', Buffer.from('7C45C93E-5216-48ED-88D4-6EB7C413'), Buffer.from('myinitialization') ); let decrypted = decipherIV.update( 'bwNBr+/yfYYPoE2aq/zqfzp6kxctfFC+MCgacwwkJSQ=', 'base64', 'utf8' ); decrypted += decipherIV.final('utf8'); console.log(decrypted); // Will output "This string is encrypted" ```