Skip to main 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.

Basic syntax

Liquid uses two main syntax structures:
  1. Output Tags: {{ ... }} — Display data from a variable or object.
  2. Logic Tags: {% ... %} — Execute conditional statements or loops.

Conditionals

Operators

  • ==, !=, >, <, >=, <=
  • and, or
  • contains (string or array)
liquid
{% if true or false and false %}
  This is true.
{% endif %}

if, elsif, else

liquid
{%- assign userLang = subscription.language -%}
{% if userLang == "es" -%}
Hola {{ first_name }}!
{%- elsif userLang == "fr" -%}
Bonjour {{ first_name }}!
{%- else -%}
Hello {{ first_name }}!
{%- endif %}

unless

liquid
{% unless level == "1" %}
  Great job getting past the first level!
{% endunless %}

case / when

Use case / when to map a variable to specific values with a fallback else. This is useful when {{ subscription.language }} alone would generate invalid paths for unsupported languages.
liquid
{% case subscription.language %}
  {% when "es" %}https://example.com/es/page
  {% when "fr" %}https://example.com/fr/page
  {% when "de" %}https://example.com/de/page
  {% else %}https://example.com/en/page
{% endcase %}
This pattern works in the Launch URL, title, and body fields.

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.
liquid
Hello {{ first_name | default: "there" }}.

date

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
liquid
{{ bill_due | date: "%a, %b %d, %y" }}
{{ "now" | date: "%Y-%m-%d %H:%M" }}
Result
Wed, Jun 28, 23
liquid
{{ bill_due | date: "%Y" }}
Result
2023
Date formatting works on strings if they contain well-formatted dates.
liquid
{{ "March 14, 2016" | date: "%b %d, %y" }}
Result
Mar 14, 16
To get the current time, pass the special word now (or today) along with the date filter.
liquid
This message was sent at {{ "now" | date: "%Y-%m-%d %H:%M" }}.
Result
This message was sent at 2022-11-02 14:39.
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.
liquid
{{ "my GREAT title" | capitalize }}
Result
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.
liquid
{{ 1.2 | round }}
  {{ 2.7 | round }}
{{ 183.357 | round: 2 }}
Result
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.
liquid
{{ 1 | pluralize: "singular", "plural" }}
{{ 2 | pluralize: "singular", "plural" }}
1 {{ 1 | pluralize: "person", "people" }}
2 {{ 2 | pluralize: "person", "people" }}
2 {{ "2" | pluralize: "person", "people" }}
Result
singular
plural
1 person
2 people
2 people

Iteration

for loops

Repeatedly executes a block of code. For a full list of attributes available within a for loop, see the Liquid for loop documentation.
liquid
{% for product in message.custom_data.products %}
  - {{ product.name }}
  {% else %}
    Sorry, we're sold out of all products.
  {% endfor %}
Request Body
{
  "app_id": "YOUR_APP_ID",
  "template_id": "YOUR_TEMPLATE_ID",
  "custom_data": {
    "products": [
      { "name": "Sweater" },
      { "name": "Hat" },
      { "name": "Shirt" }
    ]
  }
}
Result
- Sweater
- Hat
- Shirt

(If message.custom_data.products is empty)
Sorry, we're sold out of all products.
While 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 of for loops. Also note that we prevent the usage of for loops in a few Push Channel fields: contents, headings, subtitle, apns_alert, and url

limit & offset

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.
Data
great_numbers = [1,2,3,4,5,6]
liquid
{% for number in great_numbers limit:2 %}
  {{ number }}
{% endfor %}
Result
1 2
To begin the loop at the specified index, add an offset value:
Data
great_numbers = [1,2,3,4,5,6]
liquid
{% for number in great_numbers limit: 3 %}
  {{ number }}
{% endfor %}

{% for number in great_numbers limit: 3 offset: 3 %}
  {{ number }}
{% endfor %}
Result
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.
Data
products = [
  {name:"Vacuum", type:"electronics"},
  {name:"Spatula", type:"kitchen"},
  {name:"Television", type:"electronics"},
  {name:"Garlic press", type:"kitchen"}
]
liquid
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 %}
Result
All products:
  - Vacuum
  - Spatula
  - Television
  - Garlic press

Kitchen products:
- Spatula
- Garlic press

String manipulation

Apply string filters to adjust how tag values or inline strings are displayed in messages.
CommandDescriptionExampleExample Output
replaceReplaces a substring with another string.{{ 'hello world' | replace: 'world', 'there' }}hello there
capitalizeCapitalizes the first letter of a string.{{ 'hello' | capitalize }}Hello
upcaseConverts a string to uppercase.{{ 'hello' | upcase }}HELLO
downcaseConverts a string to lowercase.{{ 'HELLO' | downcase }}hello
stripRemoves leading and trailing whitespace from a string.{{ ' hello ' | strip }}hello
strip_htmlRemoves all HTML tags from a string.{{ '<p>hello</p>' | strip_html }}hello
truncateShortens a string to a specified length, adding an ellipsis (…) if needed.{{ 'This is a long sentence' | truncate: 10 }}This is a…
truncatewordsTruncates a string after a certain number of words.{{ 'This is a long sentence' | truncatewords: 2 }}This is…
replace_firstReplaces the first occurrence of a substring.{{ 'hello world' | replace_first: 'world', 'there' }}hello there
prependAdds a string to the beginning of another string.{{ 'world' | prepend: 'hello ' }}hello world
appendAdds a string to the end of another string.{{ 'hello' | append: ' world' }}hello world
lstripRemoves leading whitespace from a string.{{ ' hello' | lstrip }}hello
rstripRemoves trailing whitespace from a string.{{ 'hello ' | rstrip }}hello

FAQ

Why is substitution not working?

  • In-App Messages do not support property substitution.
  • Tag substitution does not work when using “Send Test Message”.
  • Tag keys must be alphanumeric, or use _ and - (no periods or spaces).
  • Substitution does not appear in preview mode — send a real message to test.

When should I use default vs. if/else?

Use the default filter when only the variable value needs a fallback and the surrounding text stays the same.
liquid
Hello {{ name | default: "there" }}!
Result (name = "Jon")
Hello Jon!
Result (name is empty)
Hello there!
Use if/else conditional logic when the surrounding text, punctuation, or sentence structure also needs to change based on whether the variable exists.
liquid
{% if name %}{{ name }}, shop your favorites!{% else %}Shop your favorites!{% endif %}
Result (name = "Jon")
Jon, shop your favorites!
Result (name is empty)
Shop your favorites!
Do not use default when the sentence structure changes. For example, {{ name | default: "Shop your favorites" }}, shop your favorites would render as “Shop your favorites, shop your favorites” when the name is empty. If the fallback changes more than just the variable, use if/else.

How do I control whitespace and newlines?

Use hyphens: {{- ... -}}, {%- ... -%} to trim surrounding whitespace. See Whitespace control for more details.

How do I handle user-generated content?

Wrap user-generated text in {% raw %} and {% endraw %} to prevent Liquid parsing. See “raw” syntax.
{
  "contents": {
    "en": "{% raw %} Your user-generated content with invalid characters like {{ {% endraw %}"
  }
}

Message personalization

Overview of all personalization options including Liquid, Data Feeds, and custom events.

Data Feeds

Pull real-time data from your APIs into messages at send time.

Tags

Store key-value pairs on users for segmentation and Liquid personalization.

Templates

Create reusable message templates with Liquid personalization built in.