Overview
Liquid is a templating language OneSignal uses to insert dynamic values into messages at send time. You write Liquid expressions in supported fields; OneSignal replaces them with the matching value for each recipient just before delivery.Liquid
| Syntax | Purpose | Example |
|---|---|---|
Output tags {{ ... }} | Print a value (with optional filters). | {{ user.tags.first_name }} |
Logic tags {% ... %} | Run a conditional, loop, or assignment. | {% if level > 5 %} ... {% endif %} |
journey.*, message.*, subscription.*, or user.* objects), and the push data field does not support Liquid at all. See Supported fields by message type for the full per-channel breakdown.
Prerequisites
- A OneSignal account.
- The data you want to reference is already available to OneSignal — for example, Tags set on the user,
external_id, Custom Event properties on a Journey, orcustom_datain the Create Message API. - A message or template where you can edit the field that will use Liquid.
Conditionals
if, elsif, else
Render different content based on a value.
Liquid
unless
Inverse of if — runs the block when the condition is false.
Liquid
Tag values are stored as strings. If you compare a numeric tag, either compare against a string (
level == "1") or cast first with {% assign n = level | plus: 0 %} and then compare numerically.case / when
Map a single variable to specific values with a fallback else. Useful when interpolating the variable directly would produce invalid output (for example, an unsupported language code in a URL).
Liquid
Operators
You can use these operators insideif, elsif, and unless.
| Operator | Meaning |
|---|---|
==, != | Equal, not equal |
>, <, >=, <= | Numeric comparison |
and, or | Logical AND / OR |
contains | Substring (string) or membership (array) |
if blocks if you need explicit grouping.
Liquid
(false and true) or true, you would expect it to render — but Liquid groups the rightmost two operands first, so the final check is false and ....
Variables
assign
Create a reusable variable inside a template. Useful for shortening references and for casting tag values to numbers.
Liquid
Bracket notation for special characters
Use bracket notation when a key contains spaces, hyphens, or other non-alphanumeric characters.Liquid
Filters
Apply filters with{{ variable | filter }}. You can chain filters: {{ name | downcase | capitalize }}.
default
Render a fallback value when the input is empty, null, or undefined.
Liquid
date
Format a timestamp using strftime directives. The input can be a Unix timestamp, a parseable date string, or the special words "now" / "today".
"now" is evaluated per recipient at send time. In test sends, it reflects when the test was sent.bill_due: 1687968776.
Liquid
Result
Liquid
Result
Liquid
Result
round
Round a number to the nearest integer, or to a specified number of decimal places.
Liquid
Result
pluralize
Return the singular or plural form of a string based on a count. The count must be a whole number; strings that look like whole numbers are accepted.
pluralize is a OneSignal addition — it is not part of the standard Shopify Liquid filter set. Use it inside OneSignal templates only.Liquid
Result
Math filters
Useful for casting strings to numbers and for arithmetic inside templates.| Filter | Example | Result |
|---|---|---|
plus | {{ "2" | plus: 0 }} | 2 (cast to number) |
minus | {{ 10 | minus: 3 }} | 7 |
times | {{ 4 | times: 5 }} | 20 |
divided_by | {{ 20 | divided_by: 4 }} | 5 |
modulo | {{ 10 | modulo: 3 }} | 1 |
Array filters
Used with arrays fromcustom_data, Custom Event properties, or Tags.
| Filter | Description | Example |
|---|---|---|
size | Number of items in an array (or characters in a string). | {{ cart.size }} |
first | First item. Equivalent to [0]. | {{ cart.first.product_name }} |
last | Last item. | {{ cart.last.product_name }} |
where | Filter to items whose property matches a value. | See where example below. |
join | Concatenate array items with a separator. | {{ message.custom_data.skus | join: ", " }} |
split | Split a string into an array. | {{ "a,b,c" | split: "," }} |
where example
Given a list of products, build a sub-list containing only the kitchen ones:
Liquid
Custom data
Result
String filters
Apply string filters to adjust how values render.| Filter | Description | Example | Output |
|---|---|---|---|
replace | Replace every occurrence of a substring. | {{ "hello world" | replace: "world", "there" }} | hello there |
replace_first | Replace only the first occurrence. | {{ "hello world world" | replace_first: "world", "there" }} | hello there world |
capitalize | Capitalize the first character; lowercase the rest. | {{ "my GREAT title" | capitalize }} | My great title |
upcase | Convert to uppercase. | {{ "hello" | upcase }} | HELLO |
downcase | Convert to lowercase. | {{ "HELLO" | downcase }} | hello |
strip | Remove leading and trailing whitespace. | {{ " hello " | strip }} | hello |
lstrip | Remove leading whitespace. | {{ " hello" | lstrip }} | hello |
rstrip | Remove trailing whitespace. | {{ "hello " | rstrip }} | hello |
strip_html | Strip HTML tags. | {{ "<p>hello</p>" | strip_html }} | hello |
truncate | Shorten a string to N characters; appends …. | {{ "This is a long sentence" | truncate: 10 }} | This is a… |
truncatewords | Shorten a string to N words; appends …. | {{ "This is a long sentence" | truncatewords: 2 }} | This is… |
prepend | Add a string to the beginning. | {{ "world" | prepend: "hello " }} | hello world |
append | Add a string to the end. | {{ "hello" | append: " world" }} | hello world |
url_encode | Percent-encode a string for use in a URL. | {{ "a b" | url_encode }} | a%20b |
escape | HTML-escape a string. | {{ "<a>" | escape }} | <a> |
Iteration
for loops
Iterate over an array of items. For the full attribute list, see the Liquid for loop documentation.
Liquid
Custom data
Result
{% else %} branch renders when the array is empty or undefined:
Result (products is empty)
forloop variables
Inside a for block, Liquid exposes a forloop object with metadata about the current iteration.
| Variable | Description |
|---|---|
forloop.index | Current iteration, starting at 1. |
forloop.index0 | Current iteration, starting at 0. |
forloop.first | true on the first iteration. |
forloop.last | true on the last iteration. |
forloop.length | Total number of iterations. |
Liquid
limit and offset
Restrict how many items a loop iterates over and where it starts.
Liquid
Result
FAQ
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
Result (name = "Jon")
Result (name is empty)
if/else when the surrounding text, punctuation, or sentence structure also needs to change.
Liquid
How do I control whitespace and newlines?
Use hyphens inside the tag delimiters to trim surrounding whitespace:{{- ... -}} and {%- ... -%}. See Whitespace control for the full rules.
How do I include literal {{ or {% in the output?
Wrap the section in {% raw %} and {% endraw %} so Liquid does not parse it. This is the right tool for user-generated content that may contain Liquid-looking characters. See Liquid raw syntax.
Push payload
What happens if a Liquid expression references a missing value?
The expression renders as an empty string and the message still sends. Wrap optional values in| default: "fallback" so the surrounding sentence still reads correctly.
Related pages
Personalize with properties
Use Tags, External ID, and other stored properties to personalize messages with Liquid.
Personalize with Custom Events
Reference Custom Event properties in Journey messages with
journey.first_event and related objects.Personalize with API custom_data
Send dynamic, message-specific data through the Create Message API and render it with Liquid.
Data Feeds
Pull real-time data from your APIs into messages at send time.
Dynamic Content with CSV
Personalize push, email, and SMS at scale using CSV uploads and the
dynamic_content Liquid object.