Handlebars and Dynamic Content

Use handlebars to insert dynamic content and send personalized messages to each member of your audience. Pass variables to templates, determine the message text you want to show to each recipient based on those variables, and even iterate over objects and arrays of message variables.

You can use Airship’s handlebarAirship’s message personalization syntax using double curly braces, more commonly known as {{handlebars}}. Use handlebars to insert variables and conditional logic in messages and templates. syntax to personalize templates and messages based on information associated with your audience. When you send a message, Airship evaluates the conditions in your template or message, and issues personalized Dynamic ContentVariable message content using handlebar syntax that you populate at send time. Use dynamic content to personalize messages for each member of your audience. to each member of your audience.

You can personalize messages using:

Dynamic content uses handlebar syntax:

{{#eq merge_field "string value"}}
    If merge_field contains "string value", this string will appear!

Basic Syntax and Merge Fields

Handlebar syntax works by wrapping operators in curly braces. Merge fields are the most basic way to use handlebars. Insert your merge field into templates by wrapping the name of your field in curly braces — {{merge_field}}. Airship will replace the merge field (and braces) with the data specified by the merge field at send time — just like mail merge in your standard word processor or mail client.

URL Encoding

Some properties in Airship messages expect URLs — message actions, image and “link” properties in the Interactive Editor, etc. For these fields, items in double-curly braces are URL-encoded e.g. my value becomes my%20value. Use triple braces to escape URL encoding — {{{url}}}.

  • Use double braces to reference parameters that need URL encoding: https://www.example.com/user/{{user_name}}/{{cart_id}}
  • Use triple braces when referencing a variable or complete URL that is already URL-safe: {{{url}}}

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}}.

Push example
    "audience": {
        "named_user": "one_user"
    "notification": {
        "android": {
        	"template": {
        		"fields": {
        			"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" ]

The Default Handler

If you send a message and the value for a merge field is empty or doesn’t exist, your message won’t make sense. Use $def to set a default value for one or more merge field variables, so that your message will still make sense if your merge field is empty or doesn’t exist.

{{$def key1 key2 key<x> "value if keys are empty"}}

In handlebars using a default, you’ll provide the variables you want to use in your message and the default value if your variables are empty.

The example below uses the value you if the name event property is empty or does not exist.

{{$def name "you"}}

Not Statements

A not statement renders content if a merge field does not exist, is empty, or 0. This is a simple way to test for the availability of a merge field.

{{#not movies}}You might be interested in our new TV shows!{{/not}}`

If/Else Statements

If/else statements conditionally render blocks of content. *If and else if statements render content when the condition is true, and else renders content when all other conditions 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.

{{#if fav_movie}}
   Your favorite movie is {{fav_movie}}.
   Maybe you'd enjoy a book instead.

You do not need to explicitly declare if when testing the value of the fav_movie variable.

{{#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.

Unless — The Negative If Statement

Unless renders content in a template if a condition is false — like a negative if condition. You might want to use unless when you only really need the else statement for a condition.

In this example, Airship will render message content if the condition dislikes_indiana_jones is empty or false.

{{#unless dislikes_indiana_jones}}You'll probably like Indiana Jones{{/unless}}

Equality Operators

Equality operators let you test the value of a merge field — whether it is equal to, or not equal to, specific value. Equality operators work well with string (text) values.

In this example, the message is different if the category merge field contains the value Accessories.

{{#eq category "Accessories"}}
   Enjoy up to $25 off!
   Get up to $75 off!

Use the not equal (neq) operator to render content when a merge field is not equal to a value.

{{#neq things "thing 1"}}You must be thing 2!{{/neq}}

Greater Than and Less Than

Use the following operators to show content if the value in a field — or the length of a string/array in a field — is greater than or less than a particular value.

  • {{#gt}} — Greater than
  • {{#lt}} — Less than
  • {{#gte}} — Greater than or equal to
  • {{#lte}} — Less than or equal to

String and array lengths are 0-indexed. For example, if a string has 3 characters, {{#gte field 2}} will be true.

The following example displays text if “quantity” is less than 2.

{{#lt quantity 2}}You have < 2 items in your cart{{/lt}}

Combining Operators

Use and and or to test multiple conditions for a block of content. The and and or operators help you string together complex conditions to show the right content to the right members of your audience.

and renders a block of content if all conditions are true.

{{#and (eq genre "action") (eq era "80s") }}
   You will definitely like Indiana Jones!

or renders a block of content if any of the conditions are true.

{{#or (eq movies "action") (eq era "80s") }}
   You might like Indiana Jones!

Object and Array 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 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).


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.[1].image.

Object and Array Notation for Inline lists

See: Inline ListsAn ad-hoc, CSV-formatted list of email, SMS, or open channel addresses that you want to register and/or send a message to. Unlike static lists or segments, you upload this list when creating your message; Airship registers new addresses in the list as channels when you send your message. .

For Inline lists, you can include complex arrays and objects in your CSV, using object notation to represent object properties in the header.

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).


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:

someone@sample.com,2018-04-01T18:45:30,Joe Someone,Portland,OR,Rubber Gloves,Bleach Alternative
else@sample.com,2018-04-21T16:13:01,Sir Else,Seattle,WA,Flashlight,Shovel

Setting Context Using #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, you might use the with helper to limit context and simplify your template like this:

We'll ship your package to:
{{#with shippingAddress}}
   {{yourState}}, {{yourPostCode}}

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.

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:

Product feed example
{{#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}}/
    Check back later for deals!
Couldn't fetch featured products!


While the feed block grants access to variables from the feed response, you can still reference variables (or merge fieldsA variable in your message or template that you want to populate with a personalized value for each member of the audience. Merge fields use handlebar syntax — {{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.

Loop Through Objects and Arrays

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.


You cannot limit loops using handlebars. If you want to limit iterations in a loop, you must limit the array or object data that you pass to Airship.

You specify the key of the array or object that you want to start your loop from using {{#each <property>}}. You can access properties within the loop with the following handlebars.

Set a Loop Limit

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 <property> <integer>).

We think you might like:
{{#each (limit array_of_objects 2) as |obj|}}
{{obj.name}}: {{obj.desc}}

Reference 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.

{{#each suggestedProducts}}
{{qty}}x {{productName}}

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:
Everybody coming to the pizza party:
{{#each pizza_partiers}}
{{@index}}: {{.}}

Reference items in a user’s shopping cart as an array of objects:
Are you still interested in the following items?
{{#each cart}}
* {{qty}}x {{product}} for {{price}}

Math Helpers

You can add numbers and properties that resolve to numbers or integers.

You cannot nest math operations, or use other helpers inside a math helper — i.e., you cannot use $def to set a default value for a property in a math helper. Take care when using math helpers to use properties that you know exist and have number/integer values so that your message makes sense to your audience.

Add and subtract

Add and subtract helpers take an unlimited number of arguments. You can add or subtract as many numbers as fits your message.

  • Add{{add number1 number2... numberx}}
  • Subtract{{sub number1 number2... numberx}}

Add two properties
You can have {{add slices_per_person left_overs}} slices of pizza

Subract two properties
You can have {{sub slices_per_person slices_you_ate}} more slices of pizza

Multiply and Divide

Multiplication (mul) and division (div) helpers take an unlimited number of arguments. For example, {{div 4 2 2}} resolves to 1.

  • Multiply{{mul number1 number2... numberx}}
  • Divide — {{div number1 number2... numberx}}

Multiply properties
You need {{mul slices_per_person people}} total pizza slices for the party

Divide properties
Pizza partiers can each have {{div total_slices people}} slices


Find the remainder when dividing two numbers using the mod handler. This handler takes exactly two arguments.

Use mod to find a remainder
Pizza slices up for grabs: {{mod total_slices slices_per_person}}

Rounding Numbers

You can round numbers to the nearest integer using round with a single argument — the number you want to round to an integer. You can round to the nearest decimal place by adding second argument determining the decimal place you want to round to.

Round to the nearest integer
Pi is pretty close to {{round pi}}

Round to a decimal place
Pi is much closer to {{round pi 2}}

Limit String Length (Truncate)

You can limit the length of a string with the truncate helper. For example, if you have a string property with attribute ID name and value yourname, you could truncate it to {{truncate name 7 "---"}}, and it would resolve to your---.

The truncate helper takes the following properties:

{{truncate <attribute_id> <max_length> [replacement_suffix]}}

  • attribute_id — The attribute ID containing the 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.

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.

Truncate a string
How did you like your {{truncate product_name 12 "..."}}?
Tell us what you think!

Format Dates and Times

You can format date and time values to fit your audience’s needs using dateFormat, timeFormat, and datetimeFormat helpers.

For example, if you have a birthdate attribute containing the value 1983-01-31T12:36:35, you could reformat the string so that it makes more sense to your audience: {{dateFormat birthdate locale="en-US" format="SHORT"}} would resolve to 01/31/1983.

Each of the three helpers takes three arguments, all of which are required:

  1. An ISO-8601 string datetime. In general, we recommend that you use uniform datetime values since they work for all three helpers, but the actual datetime string requirements vary per helper — Optional parts of a datetime string appear in brackets:

    • dateFormat (time is optional): yyyy-MM-dd[[' ']['T'][HH][:mm][:ss][.SS][Z][XXXXX]]
    • timeFormat (date is optional): [[yyyy-MM-dd][' ']['T']]HH:mm[[:ss][.SS][Z][XXXXX]]
    • datetimeFormat: yyyy-MM-dd'T'HH:mm:ss.SS[Z][XXXXX]
  2. locale — The user’s ISO-639 2-letter language and country codes, joined by a dash, e.g. en-US. This localizes the date and time output for your audience.


    The Airship SDK gathers ua_language and ua_country attributes for your app audience. You can use these values to determine the locale for date and time formatting.

  3. format — One of FULL, LONG, MEDIUM, or SHORT, determining how to display the date-time value. Using our example of "1983-01-31T12:36:35" from above, you can output the following formats (assuming en-US locale):

    datetimeFormat formats and example output
    • FULL: Tuesday, January 31, 1983 12:36:35 PM Z
    • LONG: January 31, 1983 12:36:35 PM Z
    • MEDIUM: Jan 31, 1983 12:36:35 PM
    • SHORT: 1/31/1983 12:36 PM

    FULL and LONG formats include time zone information. If you don’t set a time zone value in your datetime string, Airship assumes Z (UTC/GMT).

    dateFormat formats and example output
    • FULL: Tuesday, January 31, 1983
    • LONG: January 31, 1983
    • MEDIUM: Jan 31, 1983
    • SHORT: 1/31/1983
    timeFormat formats and example output
    • FULL: 12:36:35 PM Z
    • LONG: 12:36:35 PM Z
    • MEDIUM: 12:36:35 PM
    • SHORT: 12:36 PM