A beginner's guide to Shopify Liquid

Liquid is the front facing template language written in Ruby by Shopify for use on Shopify stores. Here's a quick rundown on everything Liquid does on Shopify.


Tag types

Liquid has two types of tag, the output tag which is expressed like {{ this }} and the logic tag which looks like {% this %}. The output tag is used whenever you have text to show your customer, like a product title or price. The logic tag doesn't actually show anything to your customer, but can be used to determine whether or how something should happen. A good way to think about it is that logic tags 'do' and output tags 'show'.

Output tags

If you're familiar with Object Oriented Programming, you might have noticed that products and collections behave like objects, and that's because they are! If you don't know what that means, an object is simply a wrapper for a group of attributes, for example, the product object has attributes like title, price, description, images and more. Each product in your store is one instance of the product object, and as such it has it's own set of attributes, which you can show customers by using {{output tags}}.


A complete list of all Shopify objects and their attributes can be found here in the Shopify help center.


You can output any attribute of an object by following the object with .attribute in your output tag, for example if you write {{ product.title }}, Liquid will get the current product object's title and place it over the top of wherever you put the {{ product.title }} tag.

Logic tags

There are many different types of logic tags which can perform a variety of different functions, from assigning variables to performing math to iterating through objects. We'll take a look through the most common types below.

Variable tags

What happens if you want to display a product's attributes on a random page like your homepage? That's one use for a logic tag (like {% assign %}). In this case you need to tell the homepage what product is and if you take a look at the link above you'll see that we can get a product by using all_products['product_handle']. If you just needed the title, you could use an output tag like {{ all_products['product_handle'].title }}, but you'll often want to show other information such as the product's price and create a link to the product page, and asking Shopify to find the product again each time can cause limit issues or slow down your store.


Luckily Liquid has the assign tag so you don't need to request the product every time you want to show an attribute. Assign does what it says on the tin, it 'assigns' an outputable word to something you want to reference again later. If you were to instead use {% assign product = all_products['product_handle'] %} that tells the page that until you re-assign it, 'product' refers to the product object you pulled with all_products, which means you can now use output tags like {{ product.title }} and {{ product.url }} as if you were on a product page.


Assign is just one of 4 'variable' tags in liquid, the others are {% capture %}, {% increment %} and {% decrement %}. Capture tells the page that you want to store some text (a string) as a variable to use again later, so if you use {% capture my_name %}Danny Morgan{% endcapture %} then later if you output {{ my_name }} you'll get the text 'Danny Morgan'.


Increment and Decrement are both counter type variables. The first time you use increment you'll create an integer variable starting at 0, and then every subsequent time you'll increase the count of the variable you created by one, so let's say you initialize an increment variable with {% increment count_me %} when you output {{ count_me }} youll get 0, but if you use {% increment count_me %} again, now count_me will output as 1 and so on. Decrement works the same way but in reverse, except the number starts at -1 and counts down (-1 becomes -2 becomes -3 and so on).

Control flow tags

Control flow tags are used to determine whether or not something should occur. Often referred to as 'conditionals', this is where you'll find if/elsif/else statements, as well as switch cases and the often overlooked unless statement.


Control flow tags like 'if' use operators to determine a boolean value (true or false) and then execute a block based on the result. Let's take the my_name variable we assigned earlier and use 'if' to check whether or not the name is correct. We can do that by saying

{% if my_name == 'Danny Morgan' %}
  That name is correct
{% endif %}

Liquid will evaluate that if statement as true and then output the message that tells us the name is correct.

If we instead say

{% if my_name == 'Danny Smith' %}
  That name is correct
{% endif %}

Liquid will evaluate that statement as false so it will skip over the block completely and output nothing.

If we want to output something even if the 'if' statement is evaluated as false, we can use {% elsif %} or {% else %}. For the above example, we can change the code to

{% if my_name == 'Danny Morgan' %}
  The name is Danny Morgan
{% elsif my_name == 'Danny Smith' %}
  The name is Danny Smith
{% else %}
  The name is something else
{% endif %}

In this example, if the my_name variable was assigned to 'Danny Morgan' the output would be 'The name is Danny Morgan', but if the my_name variable was assigned to 'Danny Smith' the output would be 'The name is Danny Smith'. Elsif is essentially just like saying or if. If the my_name variable was assigned to 'Danny Jones' then the output would be 'The name is something else'. Else is only fired when none of the conditionals above it evaluate to true.


Note that the correct syntax is elsif and not elseif. This is just a syntax borrowed from Ruby and has been known to trip new developers up from time to time. If you have an if/elsif statement that isn't working as you intended, double check your spelling before you hit the message boards.


{% unless %} works the same way as {% if %} but in reverse, so if the my_name variable is again assigned to 'Danny Morgan' and we run the following

{% unless my_name == 'Danny Morgan' %}
  The name was not Danny Morgan
{% else %}
  The name was Danny Morgan
{% endunless %}

The output would be 'The name was Danny Morgan' because the unless statement evaluated as false.


If/Elsif statements can optionally be written as 'case swtiches' instead. In a case switch we supply a 'case' (in this example we would supply the case my_name) and then we use the keyword 'when' to tell Liquid 'When the case is this'. The above example re-written as a Liquid case switch would look like this

{% case my_name %}
{% when 'Danny Morgan' %}
  The name was Danny Morgan
{% when 'Danny Smith' %}
  The name was Danny Smith
{% else %}
  The name was something else
{% endcase %}

This performs the same function as the above if/elsif statement, but is often preferred for readability. Ultimately, it's up to you to decide which method suits you best.

Operators

Liquid contains many of the same operators that you'd find in other languages. You can use these operators to create the conditions in your control flow tags. An important thing to remember is that a single = always denotes an assignment of some kind, if you want to check whether something equals something, you need to use a double equals (==).


The operators available in Liquid are

OperatorDescriptionExample
== is equal to {% if my_name == 'Danny Morgan' %}
!= is not equal to {% if my_name != 'Danny Smith' %}
> is greater than {% if 1 > 0 %}
< is less than {% if 0 < 1 %}
>= is greater than or equal to {% if 1 >= 1 %}
<= is less than or equal to {% if 1 <= 1 %}
or one of two conditions is true {% if my_name == 'Danny Morgan' or my_name == 'Danny Smith' %}
and both of two conditions are true {% if my_name == 'Danny Morgan' and my_name != 'Danny Smith' %}
contains something contains something {% if my_name contains 'Danny' %}

Iterators

Iterators are used when you want to do something to a group, each time the iterator runs this is called one 'loop' of the iterator. In Liquid the possible iterators are {% for %}, {% cycle %} and {% tablerow %}.


The {% for %} iterator is used when you want to loop through the group and execute a block for each of them. For also contains two sub-functions, {% break %} which stops the for iterator and exits the loop at it's current state, and {% continue %} which exits the current loop but proceeds on to the next one.


A for iterator will always continue onto it's next loop unless you decide to break it early, but the continue method can be used when you want to skip running the rest of the block for the current loop.


Let's say we have a product and it's tags are this, is, my, amazing, product. We can get the tags as a group of separate tags (called an array) and loop through each one like this

{% for tag in product.tags %}
  This tag is {{ tag }}
{% endfor %}

The output will look like this

This tag is this
This tag is is
This tag is my
This tag is amazing
This tag is product

As you can see, we looped the tags individually and created an output for each one, but what if we want to stop the loop when we find the 'amazing' tag? That can be done by using

{% for tag in product.tags %}
  {% unless tag == 'amazing' %}
    This tag is {{ tag }}
  {% else %}
    Found the amazing tag
    {% break %}
  {% endunless %}
{% endfor %}

The output for that will look like this

This tag is this
This tag is is
This tag is my
Found the amazing tag

Notice the absense of the last tag? That's because we decided to break the iterator early when we found the 'amazing' tag so it didn't make it to the last loop.


If we instead wanted to just skip the amazing tag, we would do this

{% for tag in product.tags %}
  {% if tag == 'amazing' %}
    {% continue %}
  {% else %}
    This tag is {{ tag }}
  {% endif %}
{% endfor %}

The output this time will be

This tag is this
This tag is is
This tag is my
This tag is product

The amazing tag is missing from the list because we told liquid to stop the current loop and move onto the next one when it encountered that tag.


Another Liquid iterator is {% cycle %}. We can use cycle when we have a group of strings and we want to output them in order. Consider the following

{% for tag in product.tags %}
  {% cycle 'The first tag is', 'Another tag is', 'This tag is', 'My favourite tag is', 'The final tag is' %} {{ tag }}
{% endfor %}

The result of that iterator is

The first tag is this
Another tag is is
This tag is my
My favourite tag is amazing
The final tag is product

Notice that the output went through the cycle in order. This can be incredibly useful when you want to create things like alternating css styles.


Finally, we have the {% tablerow %} iterator. This works just like a for iterator but it works inside a HTML table and generates a new table row on each loop.


In this example

<table>
  <thead>
    <tr>
      <th>Tag</th>
      <th>Odd or even?</th>
    </tr>
  </thead>
  <tbody>
    {% tablerow tag in product.tags %}
      <td>{{ tag }}</td>
      <td>{% cycle 'odd', 'even' %}</td>
    {% endtablerow %}
  </tbody>
</table>

The output looks like this

<table>
  <thead>
    <tr>
      <th>Tag</th>
      <th>Odd or even?</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>this</td>
      <td>odd</td>
    </tr>
    <tr>
      <td>is</td>
      <td>even</td>
    </tr>
    <tr>
      <td>my</td>
      <td>odd</td>
    </tr>
    <tr>
      <td>amazing</td>
      <td>even</td>
    </tr>
    <tr>
      <td>product</td>
      <td>odd</td>
    </tr>
  </tbody>
</table>

The tablerow tag has created a new row for each of our tags and by combining it with the cycle tag we can determine if the tablerow is odd or even.

Further reading

The information in this tutorial is a good place to start if you're new to Liquid and making themes. It will help get you going and solve most of the problems you're likely to encounter as a beginner, but there is much more to Liquid than just what you see here. Once you've mastered these tags, I completely recommend that you study the following resources to really get going.