Personalization
Introduction
Personalization allows you to tailor messages based on each profile's characteristics and actions. You can personalize content by:
Referencing profile attributes or trigger event attributes (for Automations).
Applying formatting, such as capitalizing a last name or displaying a birthdate in a specific format.
Using conditions to customize content (e.g., showing different messages based on country or loyalty level).
Implementing loops to dynamically display multiple items (e.g., all products in a profile’s cart or recently purchased items).
[Coming soon] Referencing external attributes from Catalogs (e.g., showcasing top products or articles in a newsletter).
Personalization is available across all channels and orchestration types (Campaigns, Recurring Automations, and Trigger Automations). All message text fields support personalization, except the email sender field.
Personalization global behavior
Batch dynamically replaces personalized attributes with profile or event specific values at the time of sending, ensuring each recipient receives a tailored message.
During message creation, personalization code can be modified by clicking on the message content and previewed by selecting a specific profile. You can also send a test message based on the selected profile. If no profile is selected, attributes will be treated as empty in the send test.
There are two ways to reference and format attributes:
Using the
{...}
Insert Variable button (also called Dynamic Content for email): This opens a guided modal that helps you select and format attributes, automatically inserting the correct code into your message.

Writing directly in Batch syntax: This allows for more advanced formatting options and greater flexibility when referencing and formatting attributes.
Referencing attributes
First of all, to be available for personalization and appear in the selection modal, an attribute must be enabled in the Custom Data section. Learn more about Custom Data here.
Two types of attributes can be referenced in personalization:
Profile Attributes
Available in all orchestration types (Campaigns, Recurring Automations, and Trigger Automations), these include details such as a recipient's first name.
Trigger Event Attributes
Specific to Trigger Automations, these attributes are based on the event that triggered the automation, such as the list of products a profile just purchased.
Referencing attributes in messages
Attributes must be enclosed in double curly braces:
Profile attributes: {{ your_attribute }}
Trigger event attributes: {{ trigger_event.your_attribute }}
Example:
Handling different data types
This syntax supports most data types (string, integer, boolean, etc.), with specific behaviors for array and object attributes.
Arrays
Array attributes must be transformed before use; they cannot be inserted as-is. Example:
More formatting options for arrays are available in the Formatting attributes section.
Objects (trigger event only)
Object attributes can contain multiple properties, but only these specific properties can be referenced—never the entire object itself.
Example:
If a Trigger Event includes an attribute named delivery_address
with the following properties:
You can reference the properties like this:
Note: Object attributes are only accessible in Trigger Event Attributes.
Default values (optional)
If an attribute is missing or null, personalization output is empty by default. However, you can define a fallback value.
Example:
If the profile's special_offer
attribute is -15%
then it evaluates to:
If special_offer
is missing:
Whitespace handling
To ensure smooth message formatting, whitespace (new lines and leading spaces) before an empty attribute is automatically removed. Example:
With a firstname
attribute defined, this evaluates to:
Note the space after Hello
is kept.
If firstname
is missing, the space is removed:
This behavior can be overridden, see Whitespace control override for more information.
Audience data
If you have uploaded specific profiles with the Audience API with their own attributes.
They can be referenced in your message when the orchestration targets the corresponding audience, by using the {{ audienceAttribute(<audience name>, <attribute name>) }}
expression.
Example:
Take the following Audience API Update call:
The following message:
will result in:
Formatting attributes
You can apply formatting options to attributes to ensure they are displayed in the most relevant way. Formatting should be applied based on the attribute type.
Transformations with arguments
You may have noticed some transformations take arguments. When this is the case, there is two way to give the arguments:
Using a named argument as such:
join(separator=',')
Directly giving the value as such:
join(',')
In the following reference we will denote arguments that can be given unnamed as such: argName?: type
where the ?
indicates the argument is optional.
General transformations
Transformations for numbers only
Transformations for strings only
Transformations for arrays of strings only (previously tag collections)
Transformations for arrays of objects and arrays of strings only
Chaining transformations
It is possible to chain transformations to produce more complex results, however you need to make sure the input and output types are compatible between transformations.
For example, you can't call formatDate
on a number or even a string, the type has to be a date. Look at the table above to know what transformations are compatible.
Here is a valid example of chaining multiple transformations:
Given a user with this array of strings exams
: ["2025-10-09T14:53:54Z", "2025-10-11T16:53:54Z"]
the example evalutes to:
As you can see we take the last
value of the array which returns a string
, then pass that to date
which parses it and returns a date
type. Finally we pass that to formatDate
.
Conditions
Conditions allow you to display specific parts of your message only to profiles that meet certain criteria.
Conditions must be enclosed within {% ... %}
and follow this syntax:
You can add alternative conditions using else if
, allowing different content to be displayed based on multiple conditions. Additionally, you can use else
to define a default fallback when none of the previous conditions are met.
Example:
Suppose you want to congratulate a user based on how far he is into your game.
You could write something like this:
Now depending on the value of the user's current_level
attribute, the message will be one of the 3 possibilities.
Comparison
Conditions can be based on the presence of an attribute. Example:
Alternatively, conditions can be based on comparisons using various operators:
Note: String values should always be enclosed in single quotes.
Operators
==
compares two values for equality!=
compares two values for inequality>
returns true if the left hand side is greater than the right hand side>=
returns true if the left hand side is greater than or equal to the right hand side<
returns true if the left hand side is lower than the right hand side<=
returns true if the left hand side is lower than or equal to the right hand side
Combining comparisons
As in any programming languages, you can combine comparisons easily:
and
returns true if both the left hand side and the right and side are trueor
returns true if either the left hand side is true or the right hand side is truenot
negates a statement(
and)
to group expressions.
Handling strings containing only spaces in conditions
Strings consisting only of spaces (e.g., " "
) are not inherently empty and will evaluate as true
in if
conditions.
To treat such values as empty, use trim(nullIfEmpty: true)
, which converts them to None
:
Speaks function
To customize contents according to the profile's language, you can use the speaks function. It accepts one or more languages as parameters, and returns true if the profile's language matches one of them.
Loops
Loops allow iterating over a list of values, such as displaying a list of products a user has purchased. The for
loop enables structured iteration over arrays:
If interests
contains ["sports", "music", "travel"]
, the output will be:
Handling Arrays of Objects
An array of objects is a list where each element is an object. Arrays of objects can be nested up to three levels deep, meaning an object inside an array can contain another array of objects, and so on.
Data in arrays of objects must be accessed using a for
loop. It is not possible to reference a specific object within the array directly. Instead, the loop iterates over all objects in the array.
For an array of objects product_list such as:
the corresponding syntax is:
Combining loops and conditions
You can combine loops (for
) and conditions (if
) to dynamically personalize content based on multiple attributes.
For example, when working with Trigger Event attributes, you may need to iterate over a list of orders and, within each order, iterate over the products it contains. Additionally, conditions can be applied to display specific content based on attribute values.
Whitespace handling
Statements ({% ... %}
) always strips the trailing newlines, so there is no need to keep everything on the same line.
Example:
This evaluates to:
Depending on the current time it will change to afternoon
or evening
.
This behavior can be overridden, see Whitespace control overridefor more information.
Email container conditions

For email messages, conditions and loops can be applied directly to an entire container or structure to:
Conditions: Display the container only if the profile meets the specified condition.
Loops: Iterate over an array to display its content within the container.
Same rules apply as for inline personalization.
Advanced transformations
Variables and arithmetic
Sometimes it might be handy to compute some value and keep a reference to it so you can reuse it multiple times inside your template.
It is mainly a quality of life improvement but still useful.
For example, suppose you want to compute an expiration date based on multiple user attributes and remind the user when their subscription will expire.
Given the following rules:
premium users have 90-days subscription
users subscribed to the newsletter have a 75-days subscription
users younger than 25 and not premium have a 60-days subscription
all other have a 50-days subscription
You could write something like this:
This evaluates to:
Obviously the date will change based on what kind of subscription the user has.
There are a couple of new features here:
Defining a variable with
set $variableName = <expression>
. A valid expression has to return a single value of any type.Arithmetic. You can do simple math inside a template. Conveniently, you can add also do arithmetic on a date by using a duration.
Concatenation. You can concatenate multiple values into a single one using the
~
operator. I’m
Data types
Standard
Standard types include:
string. You can write a literal string by using a
'
character like this:'This is a string'
. To use a'
inside your string you need to double it like this:'It''s great'
integer. You can write a literal integer like this:
230
.float. You can write a literal float like this:
20.30
boolean. A boolean is either
true
orfalse
Date
Date is a special type that can't be created by a literal. However they are produced in a couple of cases:
if an attribute is a date (that is either a
NSDate
or ajava.util.Date
for iOS and Android respectively).by converting a UNIX timestamp (number of seconds since January 1, 1970):
{{ 1491814800|date|formatDate('yyyy-MM') }}
by using the keyword
now
which returns the date at the time of execution
Duration
Duration is a special integer with a time unit. Units can be:
days:
40d
hours:
24h
minutes:
30m
seconds:
46s
Distance
Distance is a special integer with a distance unit. It is also always positive. Units can be:
meters:
5600m
kilometers:
83km
Casting rules
You can cast values into different types by using a casting operation, provided the types are compatible. The casting operation looks like this:
Casting looks exactly like a transformation but it simply converts the original value to the type if the rules allow it.
The rules of casting are as follows:
string
✓
✓
✓
✓1
✓
✓
int
✓
✓
✓
✓2
✓
✓
float
✓
✓
✓
X
✓
✓
bool
✓
✓
✓
X
X
X
date
✓
✓2
X
X
X
X
distance
✓3
✓
✓
✓
X
X
duration
✓4
✓
✓
✓
X
X
1 The string has to follow the pattern yyyy-MM-dd'T'HH:mm:ss
or the resulting date will be empty.
2 Casting from date
to integer
will return the UNIX timestamp in seconds. Casting from integer
to date
will treat the integer as a UNIX timestamp in seconds.
3 Distance Rules:
casting from
string
todistance
will treat the string as an integer representing the distance in meters.casting from
integer
todistance
will treat the integer as the distance in meters.casting from
float
todistance
will remove the decimal part and treat it as an integer representing the distance in meters.casting from
boolean
todistance
will treatfalse
as 0 andtrue
as 1.
You can pass a conversion distance unit as a parameter to the distance
transformation.
Valid distance units are detailed above.
Examples:
{{ '100'|distance }}
will result into the distance100m
{{ '12km'|distance('m') }}
will result into the distance12000m
{{ 2000|distance('km') }}
will result into the distance2000km
{{ 43.20440|distance }}
will result into the distance43m
{{ true|distance('km') }}
will result into the distance1km
(not that you'd ever do that)
4 Duration Rules:
casting from
string
toduration
will treat the string as an integer representing the duration in days.casting from
integer
toduration
will treat the integer as the duration in days.casting from
float
toduration
will remove the decimal part and treat it as an integer representing the duration in days.casting from
boolean
toduration
will treatfalse
as 0 andtrue
as 1.
You can pass a conversion duration unit as a parameter to the duration
transformation.
Valid duration units are detailed above
Examples:
{{ '100'|duration }}
will result into the duration100d
{{ '100h'|duration }}
will result into the duration100h
{{ '48h'|duration('d') }}
will result into the duration2d
{{ 405|duration() }}
will result into the duration405d
{{ 43.409|duration('m') }}
will result into the duration43m
{{ true|duration('s') }}
will result into the duration1s
Math rules
Math operations on integers
and float
s work as you would expect:
Will evaluate to:
There are a couple of rules for operations on date
s, duration
s and distance
s:
adding or subtracting a
duration
from adate
will return a newdate
.subtracting two
date
s will return aduration
.all operations between a
duration
and anumber
are allowed and will return aduration
in the original unit.all operations between a
distance
and anumber
are allowed and will return adistance
in the original unit.
Some unusual operators are available for your convenience:
//
divides two numbers and returns the truncated integer result. Example:{{ 20 // 7 }}
evaluates to2
.%
returns the remainder of the integer division of two numbers. Example:{{ 11 % 7 }}
evaluates to4
.**
raises the left hand side to the power of the right hand size. Example:{{ 2 ** 3 }}
evaluates to8
.
Type comparison rules
When comparing two values, they have to be of the same type otherwise it won't work.
The rules are as follows:
A
string
can only be compared with anotherstring
A
date
can only be compared with anotherdate
A
duration
can be compared with anotherduration
or anumber
. When comparing with anumber
it is treated as days; in other words{{ $myDuration == 3 }}
is equivalent to{{ $myDuration == 3d }}
.A
distance
can be compared with anotherdistance
or anumber
. When comparing with anumber
it is treated as meters; in other words{{ $myDistance == 200 }}
is equivalent to{{ $myDistance == 200m }}
A
number
can be compared with anothernumber
or aboolean
.
Example:
Whitespace control override
If the default behaviour doesn't suit you, you can force the behaviour with the following syntax:
{{+ +}}
or{%+ +%}
will force every whitespace to be kept{{- -}}
or{%- -%}
will force every whitespace to be stripped
You can mix and match of course: {{+ ... -}}
is completely valid.
Example:
Now that we forcefully remove the leading whitespace, with a first_name
defined it evaluates to:
Another example:
Here we forcefully keep the leading whitespace, with no first_name
defined it evaluates to:
Note the space is kept.
Last updated
Was this helpful?