Using Liquid syntax
How to apply personalization to message content
Liquid is a templating language supported by OneSignal to personalize messages across channels including email, push, SMS, in-app, and Live Activities. It uses tags, filters, and conditionals to dynamically customize message content.
To learn more about Liquid syntax, see the official Liquid documentation.
Supported fields for Liquid syntax
The following channels support personalization with Liquid syntax.
Email
- Subject, Reply-to, and Pre-header
- Message Body
- Image substitution in HTML blocks. Example:
<img src="{{image_url}}"/>
Push
- Title (
headings
), Subtitle, Body (contents
) - Image URL
- Launch URL. Example:
https://example.com/{{last_category_viewed}}
SMS
- Message Body (
contents
)
In-App Messages
Only Tag substitution is supported at this time. Does not accept Property substitution.
- Text, Button, Image Blocks.
Live Activities
- Within the
event_updates
,contents
andheadings
properties.
Basic syntax
Liquid uses two main syntax structures:
- Output Tags
{{ ... }}
- Display data from a variable or object. - Logic Tags
{% ... %}
- Execute conditional statements or loops.
Data sources
The data available for substitution.
Tags
Leverage Tags you set on your users.
Hello, {{ first_name }}
"first_name": "George"
Hello, George
API Custom Data
Inject custom_data
in passed via the Create message API.
Your account balance is ${{ message.custom_data.balance }}
{
"app_id": "5eb5a37e-b458-11e3-ac11-000c2940e62c",
"template_id": "be4a8044-bbd6-11e4-a581-000c2940e62c",
"custom_data": { "balance": "23.40" }
}
Your account balance is $23.40
Properties
Access predefined system properties. See Message Personalization for a full list.
Your current email address is {{ subscription.email }}
Logic Tags
Use {% if %}
, {% elsif %}
, {% else %}
, and {% unless %}
for conditional logic. Example:
{%- assign level = level -%}
{% if level == "2" -%}
Congrats on level 2, {{ first_name }}!
{%- elsif level > "2" -%}
Congrats on level {{ level }}, {{ first_name }}!
{%- else -%}
{{ first_name }}, come back to beat level 1!
{%- endif %}
"first_name": "George", "level": "3"
Congrats on making it to level 3, George!
Filters
Apply filters using {{ variable | filter }}
to adjust how the data is displayed.
Default
Assign a default value if the property is empty or does not exist.
Hello {{ first_name | default: "there" }}.
If the Tag for first_name
is blank or does not exist, the default value will be rendered instead.
Hello there.
Dates
The date filter converts a timestamp into another date format. The format for this syntax is the same as strftime. The input uses the same format as Ruby’s Time.parse.
Set dates as a unix timestamp in seconds with tags. This allows for use of both liquid syntax personalization and segmentation with Time Operators. For example, a tag might look like: bill_due : 1687968776
{{ bill_due | date: "%a, %b %d, %y" }}
Wed, Jun 28, 23
{{ event_date | date: "%Y" }}
2023
Date formatting works on strings if they contain well-formatted dates.
{{ "March 14, 2016" | date: "%b %d, %y" }}
Mar 14, 16
To get the current time, pass the special word now
(or today
) along with the date filter.
This message was sent at {{ "now" | date: "%Y-%m-%d %H:%M" }}.
This message was sent at 2022-11-02 14:39.
Current Time is based on when the message is rendered
The current time will be rendered in the message based on when the message is sent to the recipient. If you are testing the message, you will see the current time as when the test message was sent.
Capitalize
This filter makes the first character of a string capitalized and converts the remaining characters to lowercase.
{{ "my GREAT title" | capitalize }}
My great title
Round
This filter rounds a number to the nearest integer, or, if a number is passed as an argument, to that number of decimal places.
{{ 1.2 | round }}
{{ 2.7 | round }}
{{ 183.357 | round: 2 }}
1
3
183.36
Pluralize
This filter returns the singular or plural form of a string based on a given number. The number must be a whole number and can be provided as a string. Both the singular and plural forms of a string must be provided.
{{ 1 | pluralize: "singular", "plural" }}
{{ 2 | pluralize: "singular", "plural" }}
1 {{ 1 | pluralize: "person", "people" }}
2 {{ 2 | pluralize: "person", "people" }}
2 {{ "2" | pluralize: "person", "people" }}
singular
plural
1 person
2 people
2 people
Conditional Logic
Display different content depending on the presence of data.
Operators
In conditional statements, you have logical operators available to use. The operators available are:
==
equals!=
does not equal>
greater than<
less than>=
greater than or equal to<=
less than or equal toor
logical orand
logical andcontains
checks for the presence of a substring inside a string, and for the presence of a string in an array of strings.
Order of operations
In tags with more than one and
or or
operator, operators are checked in order from right to left. You cannot change the order of operations using parentheses — parentheses are invalid characters in Liquid and will prevent your tags from working.
{% if true or false and false %}
This evaluates to true, since the `and` condition is checked first.
{% endif %}
if, elsif, else
You can use logical operators such as if
, else
, and elsif
as control flow tags to create conditions that decide whether blocks of Liquid code get executed.
{%- assign userLang = user.language -%}
{% if userLang == "es" -%}
Hola {{ first_name }}!
{%- elsif userLang == "fr" -%}
Bonjour {{ first_name }}!
{%- else -%}
Hello {{ first_name }}!
{%- endif %}
language: "fr", tags: {"first_name": "George"}
Bonjour George!
unless
The opposite of if
executes a block of code only if a certain condition is not met. unless
might be similar to using the !=
not equal operator.
{% unless level == "1" %}
Great job getting past the first level!
{% endunless %}
Iteration
for loops
Repeatedly executes a block of code. For a full list of attributes available within a for
loop, refer to the for
loop object.
{% for product in message.custom_data.products %}
- {{ product.name }}
{% else %}
Sorry, we're sold out of all products.
{% endfor %}
{
"app_id": "5eb5a37e-b458-11e3-ac11-000c2940e62c",
"template_id": "be4a8044-bbd6-11e4-a581-000c2940e62c",
"custom_data": {
"products": [
{ "name": "Sweater" },
{ "name": "Hat" },
{ "name": "Shirt" }
]
}
}
// if message.custom_data.products has a value
- Sweater
- Hat
- Shirt
// if message.custom_data.products is empty
Sorry we're sold out of all products
Performance and limitations of using
for
loopsWhile powerful and flexible, the usage of
for
loops in liquid syntax can lead to poor notification delivery performance in certain rare cases. Be mindful of your usage offor
loops. Also note that we prevent the usage offor
loops in a few Push Channel fields:contents
,headings
,subtitle
,apns_alert
, andurl
limits & offsets
Limits the loop to the specified number of iterations. For example, if you only want to show 4 products in a message, you could use Limits and Offsets to specify the number of products shown.
great_numbers = [1,2,3,4,5,5]
{% for number in great_numbers limit:2 %}
{{ number }}
{% endfor %}
1 2
To begin the loop at the specified index, add an offset value:
great_numbers = [1,2,3,4,5,5]
{% for number in great_numbers limit: 3 %}
{{ number }}
{% endfor %}
{% for number in great_numbers limit: 3 offset: continue %}
{{ number }}
{% endfor %}
1 2 3
4 5 6
where
Creates an array including only the objects with a given property value, or any truthy value by default.
In this example, assume you have a list of products and you want to show your kitchen products separately. Using where
, you can create an array containing only the products that have a type
of kitchen
.
products = [
{name:"Vacuum", type:"electronics"},
{name:"Spatula", type:"kitchen"},
{name:"Television", type:"electronics"},
{name:"Garlic press", type:"kitchen"}
]
All products:
{% for product in products %}
- {{ product.name }}
{% endfor %}
{% assign kitchen_products = products | where: "type", "kitchen" %}
Kitchen products:
{% for product in kitchen_products %}
- {{ product.name }}
{% endfor %}
All products:
- Vacuum
- Spatula
- Television
- Garlic press
Kitchen products:
- Spatula
- Garlic press
FAQ
Why is substitution not working?
Property substitution doesn't work for In-App Messages at this time.
Tag substitution does not work when an in-app message is sent to test users via the "Send Test Message" buttons.
Tag substitution will not work if you have spaces, periods, or "dots" in tag keys. Only use alphanumeric characters and an underscore ("_") or hyphen ("-") in your tag keys if needed.
Tag substitution will not work in the previews. You need to send the actual message.
How to control whitespace and newlines?
Whitespace control is a part of Liquid where you can add hyphens -
inside of the Liquid syntax {{- ... -}}
and {%- ... -%}
to remove newlines and whitespace on the side of the Liquid code that generates it.
For example, with this syntax, you may see a new line in the notification above and below the text.
{% assign name = "Jon" %}
{{ name }}
{% unless level == "1" %}
Great job getting past the first level!
{% endunless %}
Jon
Great job getting past the first level!
By including hyphens -
, you can strip the whitespace. See Whitespace control for more details.
{% assign name = "Jon" -%}
{{ name -}}
{% unless level == "1" %}
Great job getting past the first level!
{%- endunless %}
Jon
Great job getting past the first level!
What if I pass user-generated content into my messages?
If you have an app that takes user-generated text and puts it into a message, sometimes that text can contain invalid liquid characters. In this case, it is best to wrap your user-generated content within the "raw" syntax.
For example:
{
"contents": {
"en": "{% raw %} Your user-generated content with invalid characters like {{ {% endraw %}"
}
}
Updated 5 days ago