Liquid is an open-source templating language created by Shopify. Braze uses it, with a few additions, in every channel: email, push, SMS, in-app, content cards and webhooks. It personalizes by reading the user profile object (attributes, events, purchases) and catalogs. Learn the essentials, keep the cheat sheet handy, and practice against a sample profile below.
Coming from Salesforce Marketing Cloud? Try the AMPscript 101. Why the two feel so different: Object Oriented Marketing.
1. Lesson 101
Ten concepts cover most real-world Braze Liquid. Every example on the right is live: edit it and the output updates as you type.
What Liquid can see
At send time Braze Liquid sees the whole user profile: standard attributes, custom attributes (including nested objects and arrays), the triggering event's properties, Canvas entry properties, catalogs and, with connected_content, any API. There are no tables to join.
Output and tags
{{ }} outputs a value. {% %} runs logic and outputs nothing by itself. That is the whole syntax.
{% assign greeting = 'Hello' %}
{{ greeting }}, {{${first_name}}}!Braze attribute syntax
Braze wraps attribute names in ${}: {{${first_name}}} for standard attributes, {{custom_attribute.${plan_tier}}} for custom ones, {{event_properties.${sku}}} for the triggering event. Missing values render as empty, not as an error.
Hi {{${first_name}}}
Tier: {{custom_attribute.${plan_tier}}}
Appointment: {{event_properties.${appointment_id}}}Variables
assign stores a value; capture stores a rendered block of text.
{% assign tier = custom_attribute.${plan_tier} | upcase %}
{% capture line %}{{${first_name}}} is on {{ tier }}{% endcapture %}
{{ line }}Filters
Filters transform a value with a pipe and can be chained: upcase, append, replace, plus, date, and Braze additions like number_with_delimiter.
{{ ${first_name} | append: ' ' | append: ${last_name} | upcase }}
{{ custom_attribute.${points} | number_with_delimiter }}Fallbacks
default handles empty and missing values in one step. Use it on every attribute that could be blank.
Hi {{${first_name} | default: 'there'}},Conditionals
if / elsif / else / endif, plus unless and case / when. Comparisons are case-sensitive. blank matches empty and missing values.
{% case custom_attribute.${plan_tier} %}
{% when 'vip' %}Your VIP concierge is ready.
{% when 'premium' %}Your premium perks are waiting.
{% else %}Upgrade today.
{% endcase %}Loops
for loops over arrays on the profile, with limit, offset, reversed and forloop.index.
{% for order in custom_attribute.${recent_orders} limit: 3 %}
{{ forloop.index }}. {{ order.order_id }}
{% endfor %}Catalogs
catalog_items fetches items by ID from a Braze catalog and stores them in items. IDs can come from the profile or the event.
{% catalog_items products {{custom_attribute.${last_sku}}} %}
Still thinking about {{ items[0].name }}? It is ${{ items[0].price }}.Connected Content
connected_content calls an API at send time and saves the JSON response to a variable. In this playground two example URLs return mock data.
{% connected_content https://api.example.com/loyalty/{{${user_id}}} :save loyalty %}
You are {{ loyalty.tier }} tier.Aborting a message
abort_message skips this user instead of sending a broken message. The reason shows up in Braze's message activity log.
{% if custom_attribute.${last_sku} == blank %}
{% abort_message('No SKU on profile') %}
{% endif %}Common mistakes
- Forgetting
default. A missing first name renders "Hi ," without an error. - Comparing with different case.
"VIP" == "vip"is false. - Using
elseiforelse if. Liquid spells itelsif. - Integer division.
{{ 7 | divided_by: 2 }}is 3; usedivided_by: 2.0for 3.5. - Writing
{{first_name}}instead of{{${first_name}}}in Braze. - Storing changing facts in Canvas entry properties. They are captured at entry; keep live data on the profile.
2. Playground
Your code runs against one sample profile stored the way Braze stores it: a single user object plus catalogs. Change the profile to test your fallbacks. Exercises are graded automatically, and the right panel shows the same task in AMPscript so you can feel the difference.
What Liquid sees: one profile object plus catalogs
This playground runs LiquidJS with simulated Braze tags (catalog_items, connected_content with mock responses, abort_message) and Braze filters. It is close to Braze, not identical. Dates use your browser's time zone.
3. Liquid cheat sheet
| Task | Syntax | Notes |
|---|---|---|
| Output | {{ value }} | |
| Logic | {% … %} | |
| Standard attribute | {{${first_name}}} {{${email_address}}} {{${user_id}}} | Braze-specific ${} form |
| Custom attribute | {{custom_attribute.${plan_tier}}} | Nested objects and arrays supported |
| Triggering event | {{event_properties.${appointment_id}}} | Action-based sends |
| Canvas context | {{canvas_entry_properties.${appointment_id}}} | Captured at entry |
| Variables | {% assign x = 'a' %}{% capture x %}…{% endcapture %} | |
| If / else | {% if a == b %} … {% elsif %} … {% else %} … {% endif %} | Also unless and case / when |
| Logic | and or == != > contains blank | Case-sensitive comparisons |
| Loop | {% for o in list limit: 3 %} … {% endfor %} | forloop.index, offset:, reversed |
| Fallback | {{ x | default: 'there' }} | Covers empty and missing |
| String filters | upcase downcase capitalize append prepend replace split slice truncate strip | |
| Math filters | plus minus times divided_by round ceil floor | divided_by on integers rounds down |
| Array filters | size first last join sort map where | |
| Dates | {{ d | date: '%A, %B %-d' }}{{ 'now' | date: '%Y-%m-%d' }} | strftime patterns |
| Braze filters | number_with_delimiter url_param_escape json_escape property_accessor | Braze additions to standard Liquid |
| Catalog lookup | {% catalog_items products sku_44 %}{{ items[0].name }} | IDs can come from Liquid: {{custom_attribute.${last_sku}}} |
| External data | {% connected_content https://… :save r %}{{ r.field }} | Mocked here for two example URLs |
| Skip this user | {% abort_message('reason') %} | |
| Reusable content | {{content_blocks.${footer}}} | Not in the playground |
Official reference: Braze Liquid documentation. Compare with the AMPscript cheat sheet.