Using Liquid markup to customize comments and email notifications Follow

If you're familiar with placeholders in Zendesk Support, then you already know something about Liquid markup. It's the templating language we use to enable them. Placeholders are used in automations, macros, targets, triggers, and widgets as containers for dynamically generated ticket and user data. What you may not know about Liquid markup is that you can also use it to customize how this data is selected and displayed as output. This is because Liquid also allows you to create simple programming logic such as case statements, if statements, for loops, and so on.

By writing simple control statements directly in the comment/description action in macros and the email user action in automations and triggers, you can accomplish in one automation, macro, or trigger what you used to have to do in multiple automations, macros, and triggers. You can also customize how comment text is presented.

A brief introduction to Liquid markup

You can find the Liquid documentation at Liquid for Designers. All of the elements of the language are described in detail. Here, however, is a brief introduction to how it works.

Liquid is a templating language for rendering email and HTML. Liquid is the mechanism that enables the automated placement of data in comments and email notifications using placeholders.

There are two types of markup in Liquid:
  • Output, which is text output contained in double curly brackets.
  • Tags, which contain the programming logic that determines how the data is expressed with placeholders.

If you simply equate output with placeholder, you're about half way to understanding what Liquid is and how it's. What you may not know about Liquid output however is that in addition to expressing ticket and user data, there are also methods available to manipulate text strings and arrays. In Liquid, these methods are referred to as filters. Using a filter you can transform text to uppercase characters, for example. But that's one of the simplest examples of what filters can be used for. See the Liquid documentation for more information.

The other half of understanding of how Liquid can be used comes from knowing what tags are and how they are used. Tags provide the programming logic that you can use to select and present data.

Using Liquid tags you can create:

  • if else statements
  • case statements
  • for loops
  • cycles
  • variable assignments

As an example, based on ticket properties you can create different responses in your business rules. This example shows how you can modify the Notify requester of received request trigger (or any other trigger that serves the same purpose) to return a response based on your business hours.

{% if ticket.in_business_hours == 'true' %}

Hello {{ticket.requester.first_name}}

Your request (#{{}}) has been received and is being reviewed by our support staff. 

To review the status of the request and add additional comments, follow the link below:


{% else %}

Hello {{ticket.requester.first_name}}

Your request (#{{}}) has been received and will be reviewed by our support staff during regular business hours (Monday - Friday, 8am - 6pm PST). 

To review the status of the request and add additional comments, follow the link below:


{% endif %}

Using a simple if...else statement, the first response is sent if the request is received during business hours and the other is sent if it is not. The if statement tests the ticket.in_business_hours property and responds accordingly. The ticket property is in the same format that you're familiar with when it's used as a placeholder, although not contained within double curly brackets for the simple reason that it's not being used as output here but rather as part of the logic determining what will be included in the comment when it's sent to the requester as an email notification.

For several other examples of how these simple statements can be used, see Using Liquid markup to support multiple languages in automations, macros, and triggers and Customizing the formatting and placement of text in comments and email notifications below.

These are just several examples of what you can do with these simple but powerful Liquid tags.

Using Liquid markup

Here are several more examples of how Liquid markup is commonly used in business rules to customize comments and email notifications.

For the complete list of data objects that can be used in your Liquid code, see Zendesk Support (placeholders) reference.

Note: You may find it convenient to test your Liquid markup in a test macro since you can see the results immediately by applying the macro to a ticket.

Using Liquid markup to support multiple languages in automations, macros, and triggers

Many companies and organizations support end-users who speak languages other than English and there are a number of ways to manage this. In this example, a case statement is used to determine what response is sent to the end-user based on their language setting. The email body of the Notify requester of received request trigger contains the following Liquid markup:

{% case ticket.requester.language %}

{% when 'Italiano' %}

Ciao {{ticket.requester.first_name}}

La tua richiesta (#{{}}) è stata ricevuta, è stato esaminato dal nostro staff di assistenza.

Per esaminare lo stato della richiesta e aggiungere ulteriori commenti, segui il link qui sotto:


{% when 'Danish' %}

Hej {{ticket.requester.first_name}}

Din anmodning (# {{}}) er blevet modtaget og bliver gennemgået af vores supportmedarbejdere.

At gennemgå status for anmodningen og tilføje yderligere kommentarer, skal du følge nedenstående link:
http:// {{ticket.url}}


{% else %} 

Hello {{ticket.requester.first_name}}

Your request (#{{}}) has been received, and is being reviewed by our support staff. 

To review the status of the request and add additional comments, follow the link below:


{% endcase %}

The language preference is set in the user's profile. Language support is defined by an administrator (on the Localization tab of the Account page). The table below shows the corresponding language property value for each of the languages available on that page.

Table 1.
Locale Language property value
Arabic (Egypt) Arabic (Egypt)
Arabic Arabic
Malay Bahasa Melayu
Catalan Català (Catalan)
Serbian (Montenegro) Crnogorski (Montenegrin)
Danish Dansk
German Deutsch
Austrian German Deutsch (Austria)
Swiss High German Deutsch (Switzerland)
Estonian Eesti keel (Estonian)
English English
Australian English English (AU)
Canadian English English (Canada)
English (Ireland) English (IE)
British English English (UK)
Spanish Español
European Spanish Español (España)
Latin American Spanish Español (Latinoamérica)
Filipino Filipino
French Français
French (Belgium) Français (Belgium)
Canadian French Français (Canada)
français (France) français (France)
Swiss French Français (Switzerland)
Croatian Hrvatski
Indonesian Indonesian
Italian Italiano
Latvian Latvian
Lithuanian Lietuvių kalba
Hungarian Magyar
Flemish Nederlands (Belgium)
Dutch Nederlands (Dutch)
Norwegian Norsk
Polish Polski (Polish)
Brazilian Portuguese Português (Brasil)
Portuguese Português (Portugal)
Romanian Romana
Slovak Slovak
Slovenian Slovenian
Serbian Srpski
Finnish Suomi (Finnish)
Swedish Svenska
Thai Thai (ไทย)
Turkish Türkçe
Vietnamese Vietnamese
Icelandic Íslenska
Czech Čeština
Greek Ελληνικά (Greek)
Russian Русский
Ukranian Українська
Hebrew עִבְרִית (Hebrew)
Hindi हिंदी
Japanese 日本語 (Japanese)
Simplified Chinese 简体中文 (Simplified Chinese)
Traditional Chinese 繁體中文 (Traditional Chinese)
Korean 한국어 (Korean)
Note: This example just shows the notify trigger. You'd also want to do the same thing for the update and solved triggers and any other business rules that generate comments and email notifications to the end-user.

In this example, we could have also explicitly declared the English text like the others ({% when 'English' %}). However, if the default language is English it's not necessary. The English text will be displayed to all users who have not otherwise chosen a language setting.

As you can imagine, you can use something like this for any number of reasons, not just to support multiple languages. For example, maybe you want custom responses for users in different organizations for some reason. If so, you use in a case statement like this.

Customizing the formatting and placement of text in comments and email notifications

By default, many of the business rules use the {{ticket.comments_formatted}} placeholder to include comments into email notifications. If you want more control over how the comments are presented to requesters, you can access more details about comments and their attachments using Liquid markup.

A comment is an element within a ticket and there are a number of placeholders available that you can use to include comments in email notifications. For example, you can include all comments, public comments, the last comment, etc (see Comment data).

If you want more control over how comments are displayed in email notifications, you can use Liquid markup and a for loop, as in this example:

{% for comment in ticket.comments %}


   {% for attachment in comment.attachments %}

   {% endfor %}

{% endfor %}

This returns the items in both arrays (ticket.comments and comment. attachments). In other words, the properties for every comment and attachment contained in the ticket.

If you want to only return the last comment, you can use the limit and offset attributes as in the following example:
{% for comment in ticket.comments limit:1 offset:0 %}
You can do a lot with arrays in for loops. Refer to the Liquid documentation (Liquid for Designers) for more details.
Have more questions? Submit a request


  • 0

    hi We have been using liquid markup to customize email responses for a while now and it works very well but I am trying to create a response where there a 2 different responses dependent on 2 different payment type tags . I am struggling to get it to work . Just keeps giving me the else response. Any ideas what I'm doing wrong?

    {% capture tags %}{{ ticket.tags }}{% endcapture %}{% case tag %}
    {% when 'amz' %}
    you will be refunded back to your amazon account
    {% when 'paypal' %}
    you will be refunded back to your paypal
    {% else %}
    you have not been charged
    {% endcase %}

  • 1

    Hi Jennifer,

    This seems like an easy enough problem, but it's actually somewhat complicated.

    For starters, capturing the "ticket.tags" placeholder is unnecessary. Capture is only used for strings and the "ticket.tags" placeholder is already a string of tags separated by spaces. If you test this after your "endcapture" tag,


    you'll see they are the same. If you want to put the tags in an array, instead, use the "assign" tag:

    {% assign tags = ticket.tags | split: " " %}

    This splits the "ticket.tags" string at each space and puts them in your "tags" array.

    Another problem is that your "case" statement is using "tag" instead of your "tags" variable, but even if you change it, you'll still be trying to evaluate the entire string. If your tags contain "amz" and "test", for example, your string will be "amz test" and "when 'amz'" will evaluate as false.

    You can use the "assign" method from above and loop through it with a "for" statement. Based on your example, it would look something like this:

    {% assign tags = ticket.tags | split: " " %}{% for tag in tags %}{% case tag %}
    {% when 'amz' %}
    you will be refunded back to your amazon account
    {% when 'paypal' %}
    you will be refunded back to your paypal
    {% else %}
    you have not been charged
    {% endcase %}{% endfor %}

    Note: in this example, "case tag" works because we assigned the item name in the "for" loop ("for tag in tags"), and the "case" statement is evaluating an individual tag instead of the whole array.

    There are some problems with this approach, though. First, since you are evaluating each tag individually, every tag that is not "amz" or "paypal" will print "you have not been charged". Similarly, if your tags contain "amz" and "paypal", it will print both of the first two statements.

    You can remove the "else" statement from the "for" loop, but then you would need to find a way to fallback to your default string if the "tags" array doesn't contain "amz" or "paypal". One way to do that would be to capture your text as a string variable, then evaluate it after the "for" loop. For example:

    {% assign tags = ticket.tags | split: " " %}
    {% assign refund = "you have not been charged" %}
    {% for tag in tags %}{% case tag %}
    {% when 'amz' %}
    {% assign refund = 'you will be refunded back to your amazon account' %}
    {% when 'paypal' %}
    {% assign refund = 'you will be refunded back to your paypal' %}
    {% endcase %}{% endfor %}

    Note: with this code, if your array contains both "amz" and "paypal", your text string will be overwritten by whatever is later in the array.

    The easiest method, though, would be to run an "if" statement on your "tags" array:

    {% assign tags = ticket.tags | split: " " %}
    {% if tags contains 'amz' %}
    you will be refunded back to your amazon account
    {% elsif tags contains 'paypal' %}
    you will be refunded back to your paypal
    {% else %}
    you have not been charged
    {% endif %}

    This will check to see if any items in the array match "amz", then "paypal", then return your default text if both are false. If your array contains both tags, "amz" will return as true first and break the loop.

    You can also forgo the array and just run an "if" statement on the "ticket.tags" string:

    {% if ticket.tags contains 'amz' %}
    you will be refunded back to your amazon account
    {% elsif ticket.tags contains 'paypal' %}
    you will be refunded back to your paypal
    {% else %}
    you have not been charged
    {% endif %}

    The problem here is that any tags that contain "amz" or "paypal" (for example, "notamz" or "notpaypal") will return as true. If you're confident that your tags are unique, though, this is a viable option.

    You're biggest hurdle at this point might be making sure that tickets that have the "amz" tag cannot have the "paypal" tag, and vice versa. Otherwise, make sure that whichever text string you want to default to when both are present is listed first in your "if" statement.

    Hope this helps. Let me know if you have any questions.

    Edited by Jason Littrell
  • 0

    I don't believe this works in signatures, is that true? in essence I would like to build a custom signature depending on which organization writes to me and I could do that with Liquid

  • 0

    Can you guys add this functionality to Agent signature? I tried to implement the {% ticket.requester.language %}... in Admin>Settings>Agent>Signature but it always returns the {% else %} section.

  • 0


    The common agent signature supports Liquid markup, but only for the "agent", "current_user", "account", and "dc" placeholder classes. Unfortunately, the "ticket" class isn't supported (more info here). When using Liquid in the signature, the ticket.requester.language placeholder is evaluated as null, so comparison to any other value returns false.

    If you're on the Professional or Enterprise plan, dynamic content will do exactly what you want. On the Team plan, you can build and install an app that would inject an agent signature and localized text when a comment is submitted. This would work because the Data API allows you to pull the requester's locale using ticket.requester.locale(). Aside from those options, I have a vague idea of how you may be able to accomplish this using a unique code in the signature, multiple triggers with language based codes in the notification body, and some Liquid markup directly in your email template. It's all highly theoretical, however, and quite possibly very dangerous. 

  • 0


    I need an email notification sent in a trigger to caption checked boxes within a ticket field range, and am not really clear how to use liquid mark-up for this. For a few of our applications, they have multiple options for access levels so they are check boxes. For example, there is a drop down question "System access required", which when "yes" is selected, several checkboxes appear.

    Can anyone advise how I can use liquid mark-up to only show checked boxes for that question in the email notification? The people getting the emails won't see the full content of the form.



  • 0

    Hi Sharon,

    If I understand your question correctly, you can use the following liquid code to only show checkboxes when your "System access required" field is set to "yes":

    {% if ticket.ticket_field_option_title_<field ID number> == 'yes' %} [checkboxes go here] {% else %} [fallback content goes here] {% endif %}

    Just replace <field ID number> with the id number for the "System access required" field. You can omit the {% else %} tag and fallback content if you want to leave the area blank when "yes" isn't selected. Also, the field option title is case sensitive, so "yes" =/= "Yes".



  • 0

    Hi, I'm trying to create a trigger with a liquid markup, that would send notifications based on strings in comments.  

    I have 3 triggers that send notifications. All of them are based on condition "comment text does contain the following string. For example

    If ticket 1 comment does contain 1 - send 1
    If ticket 2 comment does contain 2 - send 2
    else send 3

    Is there a solution for that?


  • 0

    Artem, could you explain more explicitly what you mean by "ticket 1 comment" and "ticket 2 comment" ? A given trigger only operates on one ticket at a time. Or are you hoping to look back at old comments on this ticket?

  • 0


    sorry for confusion, please read as follows:

    — If ticket comment does contain ABC - send 1
    — If ticket  comment does contain DEF - send 2
    — else send 3

  • 0

    @Artem I have not tested this, but something like this should work:



    {% capture comment %} {{ ticket.latest_comment }} {% endcapture %}

    {% if comment contains "text1" %}

    paragraph 1

    {% elsif comment contains "text2" %}

    paragraph 2

    {% else %}

    paragraph 3

    {% endif %}


  • 0

    @ Jonathan, 

    it works great. Thank you very much for that snippet. 


  • 0

    I've tried more or less to replicate the Liquid Markup in the second example "Using Liquid markup to support multiple languages in automations, macros, and triggers", but through much trial and error found that the string to indicate locale is not as described in the article or the example.

    Language support is defined by an administrator (on the Localization tab of the Account page) and you use the names (the exact text string) as displayed in the list of languages on that page.

    I found that locale is actually the native language name Danish is 'Dansk', Swedish is 'Svenska', and the almost impossible to guess Finnish is 'Suomi (Finnish)'.

    Please update the article and save some poor bastard hours of frustration.

  • 0

    Fellow Zendeskers - I just posted a request to try to address some of the same needs as many users have stated in the comments here- namely how to format only a portion (in my case the message section) of an otherwise "unformatted" email template. I used your post here as an example of another Zendesk users with a similar need. I'm trying to drum up support to get the Zendesk developers moving on this idea so I'd like to have you look at my post and give it a thumbs up and leave a comment there so we can bolster the case and show the need for these features.

    Please take a look:

  • 0

    If you're looking for a solution to make email notifications look a little more like "Real emails" Check out the below... i know there have been a lot of people looking for this (including myself)

  • 0

    Any plan to support Jekyl Lib.? -

    So that we could import content into the notification templates?


    It would be extremely useful, scalable for us. Just image example translations ... if I could dynamically load the notification body based on some parameters from our service, instead of having at least two email notification setup over about 150 triggers. - totally not scalable.

    Like this we could dynamically include the html content of the notifications, including the ZD placeholders ... so things would get pretty scalable, manageable .

    Any thoughts on this?

    Edited by Ferenc Bartha
  • 0

    Hi, thanks for this great article. 

    Could you tell me the language property values of Bosnian and Bulgarian? They're both languages supported in Zendesk but I can't find their property values. 


  • 0

    Hi Jack,

    I can't validate it myself, but (if those languages are supported in Liquid Markup) I believe that it should be:






    Edited by Jacob J Christensen
  • 1

    @Jack You can use the "/api/v2/locales/public.json" endpoint to pull up the data. For Bosnian its:

    "url": "",
    "id": 1008,
    "locale": "bs",
    "name": "Bosnian",
    "native_name": "bosanski",
    "presentation_name": "Bosnian - bosanski",
    "rtl": false,
    "created_at": "2010-01-11T11:54:03Z",
    "updated_at": "2016-10-27T00:09:14Z",
    "default": false

    And Bulgarian shows:

    "url": "",
    "id": 94,
    "locale": "bg",
    "name": "Bulgarian",
    "native_name": "български",
    "presentation_name": "Bulgarian - български",
    "rtl": false,
    "created_at": "2009-07-21T10:51:33Z",
    "updated_at": "2017-02-18T17:32:33Z",
    "default": false

    The "name" value is returned when using the {{ticket.requester.language}} placeholder, while the "locale" value will be returned for {{ticket.requester.locale}}.

    *Edit: for just the languages that your Zendesk instance supports, you can use "/api/v2/locales.json".

    Edited by Jason Littrell
  • 0

    Thank you Jason and Jacob :) That's awesome. 

  • 0

    Is there a way to customize a greeting depending on the time of day for Good morning and Good afternoon?

Please sign in to leave a comment.

Powered by Zendesk