---
title: "Personalisation and dynamic programmable content"
slug: "dynamic-programmable-content"
updated: 2026-06-08T21:31:28Z
published: 2026-06-08T21:31:28Z
canonical: "docs.xtremepush.com/dynamic-programmable-content"
---

> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xtremepush.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 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](https://twig.symfony.com/doc/3.x/). 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.

> [!TIP]
> The Xtremepush platform currently uses Twig 3.8.0

> [!WARNING]
> 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.

## Personalisation

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:

- Stored user [profile attributes](/documentation/docs/attributes-tags)
- Data from [real-time events](/documentation/docs/real-time-events)
- Data made available via feeds. See our dedicated [guide](/documentation/docs/dynamic-content-from-feed-source) to learn more about this feature.

### Profile attributes

Profile attributes that have been manually created, tagged into the platform from an app (see details for [Android](/documentation/docs/android-tagging#setting-user-attributes) and [iOS](/documentation/docs/ios-tagging#setting-user-attributes)) or [website](/documentation/docs/web-tagging), or synced with the platform via [API](/documentation/docs/api-tag). 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:

```Renderinganattribute
{{user_attribute_name}}
```

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

> [!TIP]
> 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:

```Personalisationofacampaign
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](/documentation/docs/attributes-tags#create-a-new-attribute-or-tag), 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):

```twig
{{attribute_name[0]}}
//To render the first item in the array
```

### Rendering a value from an array object attribute

For [array-type attributes](/documentation/docs/attributes-tags#create-a-new-attribute-or-tag), 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:

```twig
{{attribute_name.order_total}}

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

> [!NOTE]
> 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:

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

For example:

```Personalisationwithfallbackvalue
{{ 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!*

> [!TIP]
> 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:

```Attributecombinedwithtext+fallbackvalue
{{ '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](/documentation/docs/attributes-tags#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:

```twig
//To render user ID
{{user.id}}

//To render email
{{user.email}}

//To render mobile number
{{user.mobile_number}}
```

### Event properties

Properties which are passed in with an [event](/documentation/docs/web-events) 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:

```twig
{{event.property}}
```

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

```twig
{{event.property.nested_property}}
```

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

```javascript
xtremepush("event", "order_complete", {
  "order_id": "0001",
  "order_total": {
    "value": 100,
    "currency": "£"
  }
});
```

The following message, including dynamic syntax

```twig
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:

```plaintext
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-encoded* to make sure that browsers can correctly interpret the URL.

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

The filter is applied by adding `| url_encode` and an example of how this may be used can be seen below.

```twig
https://www.yoursite.com/unsubscribe/{{user.email_token | url_encode}}
```

## Snippets

Please see our [snippets](/documentation/docs/snippets) guide for more details.

## Utility functions

### Capitalisation

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

```Capitalisation
{{name | capitalize}}
```

### Date

This function generates the current date.

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

Result: January 26, 2021 16:22

Specific formats can be used too:

```Currentdatewithweekdayname
{{ "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:

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

Result: January

```Currentdate
{{ "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:

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

Result: 2022

Read Twig's documentation on [date function](https://twig.symfony.com/doc/3.x/filters/date.html) 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:

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

Full documentation for [number formatting](https://twig.symfony.com/doc/1.x/filters/number_format.html).

### 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:

```Basicslicingfunction
{{text|slice(0,50)}}...
```

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:

```Slicingfunctionwithstriptags
{{text|striptags|slice(0,20)}}
```

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.

```Slicingwithstriptagsandlengthfunction
{{text|striptags|slice(0,text|length/2)}}
// 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.

```Requiredvalues
{{ 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:

```Basicconditionalstatement
{% 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
{% if region == 'US' %} 
20 W 34th St,
New York, NY 10001,
United States
{% elseif region == 'UK' %} 
Westminster, 
London SW1A 0AA,
United Kingdom
{% else %} 
O'Connell Street Lower,
North City, 
Dublin 1, D01 TX31
Ireland
{% 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.

```twig
{% 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:

```twig
{% 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:

```AddingUTMtrackingtodifferentlyformatedURLs
{% 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 %}
```

### Loops

*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="{{entry.brand.name}} 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;">{{entry.brand.name}}{% 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:

```Contentunpackedby
{"apptoken":"app_token","id":111111,"target_by":"email","target":["[email protected]"],
  "params":{"user.first_name":"BOB","jobs":[{"job.post_name":"Job1","job.job_page_url":"https://example.com/jobs/view?1",
  "brand.name":"Brand1","brand.logo":"https://example.com/logo1.jpg","brand.location":null},
  {"job.post_name":"Job2","job.job_page_url":"https://example.com/jobs/view?2","brand.name":"Brand2",
   "brand.logo":"https://example.com/logo2.jpg""brand.location":null},
  {"job.post_name":"Job3","job.job_page_url":"https://example.com/jobs/view?3",
   "brand.name":"Brand3","brand.logo":"https://example.com/logo3.jpg""brand.location":"Dublin, County Dublin, Ireland"},
  {"job.post_name":"Job4","job.job_page_url":"https://example.com/jobs/view?4","brand.name":"Brand4",
   "brand.logo":"https://example.com/logo4.jpg""brand.location":"Dublin, Ireland"},{"job.post_name":"Job5",
   "job.job_page_url":"https://example.com/jobs/view?5","brand.name":"Brand5",
   "brand.logo":"https://example.com/logo5.jpg""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](/documentation/docs/builder-working-with-custom-html).
