Using Liquid Syntax

How to apply personalization to message content

To personalize the content of your messages, you can use Liquid Syntax. Liquid uses a combination of tags, filters, and conditional statements to ensure the right content is displayed at the right time.

You can personalize all messages, including email, push, sms, in-app, and live activities. Liquid Syntax is a templating language that OneSignal supports for messaging content. With Liquid Syntax, you can substitute names {{ name }} or apply more complex personalizations by adding logic {% if true %} .

To see more liquid syntax examples, view the source documentation maintained by Shopify.

Fields that support Liquid syntax

Email- Subject Line
- Pre-header
- Message Body
Push- Subject Line
- Message Body
- Image URL
- Launch URL (e.g{{last_category_viewed}})
SMS- Message Body
In-App- Text
- Button Text
Live ActivitiesNot Applicable


There are two ways to use Liquid syntax in your content. Double curly braces {{ ... }}will result in rendering data from a data source, object, or variable. A curly brace with a percent sign {% ... %}will render logic.

User Data Tag Include

To render a Data Tag, include the tag in curly braces using the {{ data_tag }} format.

Hello, {{ first_name }}
first_name: "George"
Hello, George

API Data Tag Include

To render values from custom_data in API notifications, call the data:

{{ message.custom_data.example_key }}

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

Logic Tags

Logic tags are used to create conditions for displaying content, and use the curly brace percent sign syntax:

{% ... %}

The content inside of these tags do not produce any visible output when the template is rendered. This lets you assign variables and create conditions or loops without printing any of the Liquid logic in the content.

{% if level == 2 %}
  Congrats on making it past the first level, {{ first_name }}!
{% elsif level > 2 %}
  Congrats on making it to level {{level}}, {{ first_name }}!
{% endif %}
first_name: "George", level: 2
Congrats on making it past the first level, George!


You can apply Liquid filters to adjust how the data is displayed. Filters are added after the variable or object, and are separated by a pipe symbol:

{{ variable | filter }}

Default Value

The most common use case for a filter is to assign a default value.

Hello {{ first_name | 'there' }}.

If the Data Tag for {{ first_name }} is blank or does not exist, the default value will be rendered instead.

Hello there.

Additionally, you can specify the default, which will show its value if the input is nil, false, or empty.

{{ product_price | default: 2.99 }}


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.

{{ bill_due | date: "%a, %b %d, %y" }}
Fri, Jul 17, 15
{{ event_date | date: "%Y" }}

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.


This filter makes the first character of a string capitalized, and converts the remaining characters to lowercase.

{{ "my GREAT title" | capitalize }}
My great title


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

Conditional Logic

Display different content depending on the presence of data.


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 to
  • or logical or
  • and logical and
  • contains 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.

{% if country == "au" %}
  G'day, {{ first_name }}!
{% elsif country == "nz" %}
  Kia ora {{ first_name }}!
{% else %}
  Hi {{ first_name }}!
{% endif %}
country: "au", first_name: "George"
G'day, George!


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


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

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


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 %}
- {{ }}
{% endfor %}

{% assign kitchen_products = products | where: "type", "kitchen" %}

Kitchen products:
{% for product in kitchen_products %}
- {{ }}
{% endfor %}
All products:
- Vacuum
- Spatula
- Television
- Garlic press

Kitchen products:
- Spatula
- Garlic press