Personalisation and dynamic programmable content

Use Twig's programmatic syntax to achieve various use cases

Xtremepush uses Twig, a template engine, for dynamic content. For further information, you can review Twig's official documentation. Dynamic content can be used with most channels (email, SMS, mobile and web push, onsite/in-app messages and inbox). Details and examples of some of the most commonly used programmatic functions and capabilities are given in the following sections.


Implementing Twig for older projects

Projects created before the 20th of July of 2020 may not have this feature enabled, or only enabled for email campaigns. Contact our support team to enable this feature.

Some considerations need to be taken into account before enabling the feature:

  • Existing campaigns prior to this feature being enabled will still not support it. Therefore, a new campaign will need to be created.
  • This change will also imply a change in behaviour: for older projects that don't have the current settings, when sending users a message with an attribute that is not set for a particular user, the result will be that the message won't be sent to that user. In this case, the error message 'not personalised' will be shown in the detail from the notifications log. With the current settings, if the campaign cannot be personalised, it will be sent with an empty string in place of the unpopulated attribute.


Personalised content can be generated when your campaign is sent. Data for personalisation can be taken from a variety of sources, which include but are not limited to:

Profile attributes

Profile attributes that have been manually created, tagged into the platform from an app (see details for Android and iOS) or website, or synced with the platform via API. Once an attribute is in the system it can be used for personalisation.

Attributes can be dates and other values like propensity scores that are used in segmentation or content decisions in templates, or they can be strings like a customer’s first name that can be used directly in personalisation.

The syntax uses double curly brackets that wrap the attribute name:


From the campaign builder, click to search for available attributes and add them in the format shown in this example.


Case sensitive attribute names

Attributes are looked up by name, so they must be referenced by the exact same name and case. For example, FirstName is not the same as firstname.

For example a message like:

Hi Sam, your balance is €20. Tap today to top-up!

May be created using the following attributes for personalisation:

Hi {{user_first_name}}, your balance is €{{user_balance}}. Tap today to top-up!

Rendering a value from an array attribute

For array-type attributes, where you pass the data as a JSON array (eg: ["value1","value2","value3], you can render the values by indicating the position of the item within the array, using following syntax (start counting from 0, not from 1):

//To render the first item in the array

Rendering a value from an array object attribute

For array-type attributes, where you pass the data as a JSON object (eg: { "order_total": "12.34", "item_count": 3, "items": [{...}, {...}, ...] }, you can render the values from the nested elements by using the following syntax:


//This would render the value `12.34`

Indicating a fallback value

It is best practice, if possible, to use a fallback value in case the attribute doesn't exist for the user.


Depending on the implementation of your project, if the attribute doesn't exist for a user the message might be sent with an empty string or not be sent altogether. Testing beforehand by targeting a user profile without the attribute stored should always be carried out.

Use the following syntax for a fallback value:

{{ user_attribute_name | default('Alternative') }}

For example:

{{ user_first_name | default('Hi') }}, check out our new feature!

If the attribute exists for the user the following message will be rendered:

Laura, check out our new feature!

Otherwise the fallback value will be rendered:

Hi, check out our new feature!


Strings can be wrapped by single quotation marks ' ' or double quotation marks " ".

The attribute can also be combined with some text, including a fallback value:

{{ 'Hello ' ~ first_name | default('there') }}

If the attribute exists for the user the following message will be rendered:

Hello Doona

Otherwise the fallback value will be rendered:

Hello there

Rendering system attributes

There are some reserved system attributes that can also be used to personalise communications with your customers. To render these you need to include user. before the attribute name.

Some common examples:

//To render user ID

//To render email

//To render mobile number

Event properties

Properties which are passed in with an event can be used in the content of messages by using a similar syntax to user attributes.

To access the value of a top level property the following can be used:


And for data nested deeper within the JSON, the following syntax can be used:


So given an event with related order data which looks something like this:

xtremepush("event", "order_complete", {
  "order_id": "0001",
  "order_total": {
    "value": 100,
    "currency": "£"

The following message, including dynamic syntax

Thank you for your order.
Reference: {{event.order_id}}.

The total of your purchase was: {{event.order_total.currency}}{{event.order_total.value}}

Would output:

Thank you for your order.
Reference: 0001.

The total of your purchase was: £100

Dynamic URLs

You can generate dynamic URLs to use profile attributes as URL parameters for links or images. When doing so, it is important that they are URL-escaped (also known as URL-encoded) to make sure that browsers can correctly interpret the URL.

If the values stored in the profile attributes are not already escaped, a filter can be used to ensure Xtremepush will do this before sending the message.

The filter is applied by adding | url_escape and an example of how this may be used can be seen below.{{user.email_token | url_escape}}


Please see our snippets guide for more details.

Utility functions


This function can be used in combination with variable content to make sure only the initial character is capitalised.

{{name | capitalize}}


This function generates the current date.

{{ "now" | date() }}
// To render  (Month DD, YYY HH:MM)

Result: January 26, 2021 16:22

Specific formats can be used too:

{{ "now" | date("l, F jS") }}
// To render day of the week, Month and calendar day

This will render day of the week, Month and day, like this: Tuesday, January 26th

The elements used within the date function can also be used individually:

{{ "now"|date("F") }}
// To render the month

Result: January

{{ "now"|date("d/m/Y") }}
// To render date in a numeric format (dd/mm/YYYY)

Result: 26/01/2019

Again, the elements used within the date function can also be used individually:

{{ "now"|date("Y") }}
// To render the year in a numeric format (YYYY)

Result: 2022

Read Twig's documentation on date function for additional options.

Format number

This function allows to change the format of a number. Amongst many other things, it can be useful to round numbers. For example:

{{ PointsBalance | number_format(2) }}
//Display only 2 decimal places

Full documentation for number formatting.

Basic Slicing

This function allows to cut short dynamic content or take a sub string from it.

A common use case is truncating dynamic text, for example if you want to cut short some text at 50 characters and add trailing dots you can use:


If the dynamic content was:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

It would become:
Lorem ipsum dolor sit amet, consectetur adipiscing...

Advanced Slicing

Include striptags within the function to remove HTML tags included in your content:


If your dynamic text is likely to vary a lot in length you can also use the length function to concatenate the text to a fraction of it's length and avoid over/under concatenating.

// To slice the text in length by half

Required values

This function prevents a message from being sent in case an important value is missing. A common use case is for transactional messages which include a unique URL or code.

{{ verification_url | required }}

When a required value is missing the message will not be sent., in which case it be displayed in the notifications log with a 'Required values are not set' error.

Conditional statements

Conditional statements (if / else) will ask the system to take an action if something is true; otherwise, do something else. The simplest version would include just an if statement and an endif to indicate the end of the conditional statement. In some cases, dynamically added information is not always present, so this can be handled with the following syntax:

{% if entry_brand_location %}, {{entry_brand_location}} {% endif %}
// If that information exists, render it, otherwise don't render anything

Within the same conditional statement, we can include alternatives (using if / else). For instance, to do regional variations in a single email if you have an appropriate region or geo attribute for your users:

{% if region == 'US' %} 
20 W 34th St,
New York, NY 10001,
United States
{% elseif region == 'UK' %} 
London SW1A 0AA,
United Kingdom
{% else %} 
O'Connell Street Lower,
North City, 
Dublin 1, D01 TX31
{% endif %}
// If the region equals "US" then render the US address, if it equals UK, render the London address and otherwise render Ireland's address.

Date comparisons

A very useful use case for conditional statements is using the value of date-based attributes to show specific content.

In the example below, different offers are displayed depending on the date relative to the value stored in the deposit_date attribute.

{% if date(deposit_date) == date("-1 day 00:00") %}

Enhanced offer text

{% if date(deposit_date) == date("+2 day 00:00") %}

Regular offer text

{% else %} 

Final offer text

{% endif %}

It is also possible to perform evaluations against specific dates. The example below compares against more than one date in the same line:

{% if registration_date == "2021-05-07T00:00:00Z" or registration_date == "2021-05-08T00:00:00Z" or registration_date == "2021-05-09T00:00:00Z"%}

Enhanced offer text

{% else %} 

Standard offer

{% endif %}

Advanced conditional statements

Another use case for conditional statements is to address inconsistency in dynamically generated links. Some links might end in a ? and some not, but we want to be able to add UTM tracking to all the links. We can handle this on the template side by using the following syntax:

{% if '?' in entry.page_url %}<a href="{{entry.page_url}}&utm_source=xp&utm_medium=email&utm_campaign=example.campaign" style="display:block; text-decoration:none;">{% else %}</a><a href="{{entry.page_urll}}?utm_source=xp&utm_medium=email&utm_campaign=blah" style="display:block; text-decoration:none;">{% endif %}


For loops can be useful for generating a section of content that has a number of similar repeating elements but whose number and content varies for users.

One common use case for this type of function is a recommendation email that runs daily or weekly and that is linked to a recommender system for products, articles or some other form of personalised content. These emails will typically have a varying number of recommendations per user per day with many different variations in content and the for loop can be used to build the personalised recommendation portion of the email for each user. This example is from an API triggered template campaign:

<p><!-- {% for entry in jobs %} --></p>
<hr noshade="noshade" color="#686868" width="100%" size="1" style="padding:0; margin:8px 0 8px 0; border:none; width:100%; height: 1px; color:#686868; background-color: #686868">
<table border="0" cellspacing="5" cellpadding="0" align="center" bgcolor="#ffffff"><tbody><tr><td width="45" rowspan="2" align="left" valign="centre"> <a href="{{entry.job.job_page_url}}" style="display:block; text-decoration:none;" data-pos="IC4AP9-t"> <img src="{{entry.brand.logo}}" alt="{{}} logo" width="70" style="border-radius: 50%;"></a> </td> <td height="30" valign="bottom"><a href="{{entry.job.job_page_url}}" style="display:block; text-decoration:none;" data-pos="IDhr4HnC"><span style="color: #63c3d1; font-size: 18px; font-weight: bold; line-height: 1; vertical-align:middle;">{{entry.job.post_name }}</span></a></td></tr><tr><td valign="top" height="30"><a href="{{entry.job.job_page_url}}" style="display:block; text-decoration:none;" data-pos="eSPO0o6O"><span style="color: #686868; font-size:13px; font-weight: lighter;"><span style="color: #686868; font-size:13px; font-weight: lighter;">{{}}{% if entry.brand.location %}, {{entry.brand.location}} {% endif %}</span></span></a></td> </tr></tbody></table>
<p><!-- {% endfor %} --></p>

Then content like the following is posted via API to target individual users with personalised recommendations. The job array can vary in size and content and is dynamically unpacked by the for loop template:

{"apptoken":"app_token","id":111111,"target_by":"email","target":["[email protected]"],
   "":"Brand3","brand.logo":"""brand.location":"Dublin, County Dublin, Ireland"},
   "brand.logo":"""brand.location":"Dublin, Ireland"},{"job.post_name":"Job5",
   "brand.logo":"""brand.location": "Dublin,Ireland"}]}}

In the drag-and-drop builder, a for loop, conditional statement, beginning and end statements can be added in custom HTML blocks to keep them separate from other content. You can see this in the example shown in our dedicated guide.