Delivering a highly personalized user experience is one way to stand out from the crowd for your users.  Phiture’s partner Braze – the leading customer engagement platform – relies on Liquid code to help their clients deliver this personalized experience. 

Originally developed by Shopify, and now an open source project on Github, Liquid is a versatile templating language written in Ruby that enables the dynamic rendering of content in different channels, such as emails, push notifications, and in-app messages. Due to its simplicity, flexibility, and ease of integration, Liquid has become a popular framework for developers to manipulate, format, and display data, allowing for the creation of tailored and customizable content.

This article explores the world of Liquid, its purpose, and use cases. We explain why decision-makers should consider incorporating Liquid into their CRM Strategies, and some common examples of its application with Braze.


The importance of personalization

We cannot underline enough how important personalization is. At a top level, 76% of consumers are more likely to consider purchasing from brands that personalize. This is also reflected in Braze’s experience of using personalization across different channels, where they recorded:

  • 40% uplift in in-app message click rates
  • 37% uplift in email click rates
  • 48% uplift in mobile push open rates

So why doesn’t everyone embrace personalization given the uplifts? Well, if a CRM Team needs to implement individual variants for each campaign, it quickly becomes very time consuming, and CRM teams typically have limited resources. Indeed, it’s with this problem that Liquid is able to play a role. 


Beyond basic personalization

While personalization is key to engaging users, just adding the first name and sending in the preferred language of the user is not enough. Liquid can help your brand resonate with each individual customer by incorporating rules based on the user’s behavior in the app/website, and dynamically generating content based on custom data and preferences. For example, using connected content or Braze catalogs to provide product recommendations based on previous purchases. This helps maintain consistency across all customer interactions and strengthens brand affinity.

The importance of good taxonomy

Scoping your app’s or website taxonomy when creating your Lifecycle Communications Roadmap is of utmost importance, as it lays the foundation for effective personalization using Liquid Code in Braze. 

A well-defined taxonomy ensures clarity and consistency, allowing CRM Teams to know which custom attributes, event properties, and API-trigger properties are available to use for personalization purposes, as well as their format and type (string, array). This will enable them to know which tags and operators from Liquid Code are most useful and ensures seamless integration with data sources that will later feed all personalized campaigns. Ultimately, a good taxonomy serves as a guiding framework for CRM Managers and empowers them to create impactful campaigns.


Examples of How to Program Liquid for an App

Basic personalization: Name, Language

Liquid provides a vast number of objects, tags, filters, and operators that help you insert information into the message, even giving the right format.  This will give consistency and a professional look across all channels. One of the most used tags is the “if” statement”, which allows you to establish rules and conditions to display (or not display) content. For example:

First, we determine the languages we want to use. We want to display the copy in Spanish or a default language (English):

{% if {{${language}}} == "es" %}
      Gracias por tu compra 
{% else %}
      Thanks for your order.
{% endif %}

Then, we check if the user has a first name on their user profile. If there’s no first name, we make sure we don’t have any blank spaces:

{% if {{${language}}} == "es" %}
¡{% if ${first_name} != blank %}{{${first_name}}}, g{% else %}G{% endif %}racias por tu compra! 
{% else %}
{% if ${first_name} != blank %}{{${first_name}}}, t{% else %}T{% endif %}hanks for your order! 
{% endif %}

Finally, we want to make sure that we have the right format, so the name is in the correct case. 

{% if {{${language}}} == "es" %}
¡{% if ${first_name} != blank %}{{${first_name} | capitalize}}, g{% else %}G{% endif %}racias por tu compra! 
{% else %}
{% if ${first_name} != blank %}{{${first_name} | capitalize}}, t{% else %}T{% endif %}hanks for your order! 
{% endif %}

The result:

If there is a first name, for example, “Alexa” , and the language is Spanish, it’ll generate “¡Alexa, gracias por tu compra!”


If there’s no first name and the language is not Spanish, for example Dutch, it will generate, “Thanks for your order!”

Other things that can be edited in a similar fashion to this example include: 

  • append
  • capitalize
  • downcase
  • first
  • last
  • upcase
  • split
  • date format


Dynamic content using event properties

By leveraging event properties, as well as custom attributes, you can dynamically insert information based on a user’s action in the app. If you have a triggered campaign, you can insert Liquid tags with all the event properties included in the triggering event. This will enable real-time messages with specific information about user behavior. For example, if you wanted to trigger an abandoned cart nudge to users who added an item but didn’t complete the purchase.

{% if {{${language}}} == "es" %}
     ¿{% if ${first_name} != blank %}{{${first_name} | capitalize}}, a{% else %}A{% endif %}ún estás interesado en comprar {{event_property.${product_name} | capitalize}}?
{% else %}
     {% if ${first_name} != blank %}{{${first_name} | capitalize}}, a{% else %}A{% endif %}re you still interested in buying {{event_property.${product_name} | capitalize}}. 
{% endif %}

In this script we are: 

  • Asking the user’s preferred language. If the user doesn’t speak Spanish, we show the default (English).
  • We’re formatting the copy to display the first name of the user (if any) with the proper case.
  • We’re adding the name of the product the user added to their cart but didn’t buy.


The result:
Alexa, are you still interested in buying Cool T-Shirt?

Highly personalized messages 

Liquid code’s power is truly evident when you utilize Control Flows, Iterations, Templates, and Variables tags.

  • Control Flows, create conditions that decide whether blocks of Liquid code get executed. Here’s where we use “if” statements, the most commonly used and useful tag we will use.
  • Iterations, repeatedly run blocks of code. One of the most common tags is “for,” which is commonly used to find objects inside an array. Check out the example below.
  • Templates tell Liquid where to disable processing for comments or non-Liquid markup, and how to establish relations among template files. “comment” is the most used one: {% comment %} Your comment here {% endcomment %}
    Variables create new variables. For example, “assign” and “capture.”

Combining these with Braze’s features, such as connected content, promotional codes, and catalogs, you can build automated flows where users receive tailored recommendations, timely messages, and highly personalized content in real time with less effort on the part of the CRM Team. That’s because once set up, it’s continuously maintaining and monitoring performance.

For example, based on the user’s order, you can recommend more products, encouraging re-purchases. This could be a dynamic product carousel displayed as in-app messages.

{% connected_content :save response %}
{% assign favoriteProducts = {{custom_attribute.${favorite_products}}} %}
{% assign productids = {{[0].content.productids}}%}
{% assign productbought ={{event_property.${product_name}}} %}

{% for favorite_product in {{favoriteProduct}} %}
  {% assign id = {{ids[favorite_product["id"]]}} %}
  {% if {{id}} != nil %}
   {% break %}
  {% endif %}
{% endfor %}
{% if id == nil %}
{% abort_message('No match found') %}
{% else %}
People that bought {{productbought}} also liked {{id.ProductName}}
{% endif %}

Here we’re using the %for% loop to find a match between the product ids in the catalog or connected content call, and the “ids” inside the array favoriteProducts. If it matches, you send the message.


The result: 

People that bought Cool T-Shirt also liked Awesome Sweatshirt. 

Another interesting use case is using Liquid Code for Dynamic pricing, the practice of having multiple price points based on a few critical factors, such as user behavior or characteristics. You can read more about this strategy and how to implement it in Braze here

Transactional messages using API-triggered campaigns

Transactional API-Triggered campaigns are set up with Braze where the CRM manager takes care of all the settings except the actual sending, as the IT Team or Developer usually performs this task. 

API-triggered campaigns are ideal for more advanced transactional use cases. These allow marketers to manage campaign copy, multivariate testing, and re-eligibility rules within the Braze dashboard while triggering the delivery of that content from their servers and systems. The API request to trigger the message can also include additional data to be templated into the message in real-time (similar to event properties).

To facilitate and make the code more readable, when using Liquid Code it’s better to assign tags to all the items from the API call at the beginning of the HTML and later just name them in the HTML body. 

For example, this is the information you’re receiving from the API call (payload)

"campaign_id": "YOUR-CAMPAIGN-ID",
        "recipients": [

             "external_user_id": user_1234,
             "trigger_properties": {
               "first_name": "John",
               "language": "en",
               "order_number": "12345",
               "order_items": [
                  "product_id": "product_1",
                  "product_name": "Product 1",
                  "quantity": "1",
                  "price": "32.99",
                  "price_formatted": "32,99 €",
                  "image_link": "image.png",
                  "product_link": ""

Then, in the HTML, you can assign a tag to each item and make the content fully dynamic. 

{% assign fname = {{api_trigger_properties.${first_name}}} %}   
{% assign language = {{api_trigger_properties.${language}}}%}
{% assign order_id = {{api_trigger_properties.${order_number}}}%}
{% assign product_name = {{api_trigger_properties.${order_items.product_name}}}%}
{% assign price = {{api_trigger_properties.${order_items.price_formatted}}}%}
{% assign quantity = {{api_trigger_properties.${order_items.quantity}}}%}

<!DOCTYPE html>
      <meta charset="utf-8">
      <title>Order Confirmation - {{ order_ide }}</title>
            <p>Dear {{ fname }},</p>
            <p>Thank you for your order!</p>

                     <th>Product Name</th>
                {% for order_item in {{api_trigger_properties.${order_items}}} %}
                      <td>{{ product_name}}</td>
                      <td>{{ quantity }}</td>
                      <td>{{ price | money }}</td>
                   {% endfor %}
                    <td>{{ price | money }}</td>
       <p>If you have any questions or concerns about your order, please don't hesitate to contact us.</p>
       <p>Thank you for shopping with us!</p>

For order confirmation emails, consider using %for% statements to make the HTML dynamic and display all products purchased. For example:

{% assign order_items = {{api_trigger_properties.${order_items}}} %} 
{% for order_item in order_items %}
    {% assign product_id = {{order_item.product_id}} %}     
    {% assign img = {{order_item.image_link}} %} 
    {% assign url = {{order_item.product_url}} %}
    {% assign price = {{order_item.price}} %}
    {% assign quantity = {{order_item.quantity}} %}
    {% assign price_formatted = {{order_item.price_formatted}} %}
    {% assign voucher = {{order_item.used_voucher}} %}
    {% assign code = {{order_item.voucher_code}} %}
    {% assign alt = {{order_item.product_name}} %}
    {% assign product_name = {{order_item.product_name}} %}

     <!-- 1. Space Column left 10px -->
     <html info> 
     <!-- 2. Column Product image --> 

     <a href="{{url}}">

     <!-- 3. Column Product name -->
       <HTML info> {{product_name}}
     <!-- 4. Column Quantity -->
       <HTML info>{{quantity}}  
     <!-- 5. Column Price -->
       <HTML info>{{price_formatted}}
     <!-- 6. Space Column right 10px -->    
       <HTML info>

{% endfor %}

An example of an order confirmation message with Liquid


Incorporating Liquid into your CRM strategy is a game-changer for delivering a highly personalized and engaging customer experience. Using Liquid goes far beyond basic personalization, allowing you to create tailored messages with dynamic content based on the user’s behavior and characteristics. Liquid Code can empower your App to connect with your customers on a deeper level, driving increased engagement, conversion, and brand loyalty. As a Braze partner and heavy users of Liquid, we have witnessed the impact of Liquid in CRM Strategies, allowing teams to be more efficient when creating campaigns, unlocking Braze’s full potential, and rocketing your App to success. 


Before you go

  • Need support to understand how to deploy Liquid Code to supercharge your CRM strategy? Reach out to us here. 
  • Phiture’s Subscription Stack is designed to help growth marketers conceptualize their subscription optimization strategy and understand the essential components of running a mobile subscription business. CRM is vital across the stack, and you can check it out here. 
  • Phiture’s Mobile Growth Stack Slack Community brings together professionals from around the world, who are engaging with technology like Liquid to supercharge their CRM strategy. Join today to stay on top of the latest industry updates and trends, pose questions (or answer them), and connect with fellow mobile growth marketers. 


Further Reading

Read Github’s guide to Liquid Code.

Braze’s guide to personalization using liquid tags. 

Read Braze’s guide to API campaigns.