Skip to content

Developers · Liquid

Liquid Tags: Loops

Last updated 2 November 2026

This article discusses the following topics:

  • for
  • cycle
  • ifchanged
  • tablerow

for

Liquid allows loops over collections to iterate over arrays, hashes, and ranges of integers.

Iterating Over an Array

{% for item in array %}
  {{ item }}
{% endfor %}

Iterating Over a Hash

When iterating a hash, item[0] contains the key, and item[1] contains the value:

{% for item in hash %}
  {{ item[0] }}: {{ item[1] }}
{% endfor %}

Instead of looping over an existing collection, you can also loop through a range of numbers. Ranges look like — parentheses containing a start value, two periods, and an end value. The start and end values must be integers or expressions that resolve to integers.

Input if item.quantity is 4

{% for i in (1..item.quantity) %}
  {{ i }}
{% endfor %}

Output

1,2,3,4

Breaking and Continuing

You can exit a loop early with the following tags:

  • : immediately end the current iteration, and continue the loop with the next value.
  • : immediately end the current iteration, then completely end the loop.

These are only useful when combined with an statement.

{% for page in pages %}

{% comment %}
  Skip anything in the hidden_pages array,
  but keep looping over the rest of the values
{% endcomment %}

  {% if hidden_pages contains page.url %}
    {% continue %}
  {% endif %}

{% comment %} If it's not hidden, print it. {% endcomment %}

  * [page.title](page.url)
{% endfor %}
{% for page in pages %}
  * [page.title](page.url)

{% comment %}
  After we reach the "cutoff" page,
  stop the list and get on with whatever's after the "for" loop:
{% endcomment %}

  {% if cutoff_page == page.url %}
    {% break %}
  {% endif %}

{% endfor %}

Helper Variables

During every loop, the following helper variables are available for different styling needs:

VariableDescription
forloop.length
The length of the entire for loop.
forloop.index
The index of the current iteration.
forloop.index0The index of the current iteration (zero-based).
forloop.rindexReturns number of remaining items.
forloop.rindex0Returns number of remaining items (zero-based).
forloop.firstIs this the first iteration?
forloop.lastIs this the last iteration?
forloop.parentloopProvides access to the parent loop, if present.

Find examples on the Liquid Objects page.

Optional Arguments

There are several optional arguments to the tag that can influence which items you receive in your loop and what order they appear:

  • lets you restrict how many items you get.
  • lets you start the collection with the nth item.
  • iterates over the collection from last to first.

Restricting Elements

array = [1,2,3,4,5,6]

{% for item in array limit:2 offset:2 %}
  {{ item }}
{% endfor %}

Output

3,4

Reversing the Loop

{% for item in collection reversed %}
  {{ item }}
{% endfor %}

Handling an Empty Collection

A loop can take an optional clause to display a block of text when there are no items in the collection:

items => []

{% for item in items %}
  {{ item.title }}
{% else %}
  There are no items!
{% endfor %}

cycle

Loops through a group of strings and outputs them in the order that they were passed as parameters. Each time is called, the next string that was passed as a parameter is output.

{% for i in (1..4) -%}
  {% cycle "one", "two", "three" %}
{% endfor -%}

will result in:

one
two
three
one

Though is often used within a loop block, it also works on its own by placing multiple copies of the tag on the page. Liquid treats each tag as part of the same group, even if their contents differ.

{% cycle "one", "two", "three" %}
{% cycle "banana", "apple", "orange" %}
{% cycle "one", "two", "three" %}
{% cycle "one", "two", "three" %}

will result in:

one
apple
three
one

If you want total control over groups, you can optionally specify the group's name. This can even be a variable.

{% cycle "group 1": "one", "two", "three", "four" %}
{% cycle "group 1": "one", "two", "three", "four" %}
{% cycle "group 2": "one", "two", "three", "four" %}
{% cycle "group 2": "one", "two", "three", "four" %}
{% cycle "group 1": "one", "two", "three", "four" %}
{% cycle "group 1": "one", "two", "three", "four" %}

will result in:

one
two
one
two
three
four

Liquid assumes that each tag without a name is part of the same nameless group. For example:

{% cycle "A", "B", "C", "D" %}
{% cycle "group 3": "one", "two", "three", "four" %}
{% cycle "group 4": "one", "two", "three", "four" %}
{% cycle "group 4": "one", "two", "three", "four" %}
{% cycle "group 3": "one", "two", "three", "four" %}
{% cycle "group 3": "one", "two", "three", "four" %}
{% cycle "A", "B", "C", "D" %}

will result in:

A
one
one
two
two
three
B

ifchanged

The tag is typically called inside a loop because it executes a block of code but only outputs the result if this is the first time it was called or if the previous call had a different result.

Input

{% assign list = "1,1,1,1,3,3,3,2,1,3,1,2" | split: "," %}
{% for item in list -%}
    {%- ifchanged %} {{ item }} {% endifchanged -%}
{%- endfor %}

Output

1 3 2 1 3 1 2

tablerow

Generates rows for an HTML table. Must be wrapped in opening and closing HTML tags. The tablerowloop object describes the full list of attributes available within a .

Input
<table>
  {% tablerow product in collection.products %}
    {{ product.title }}
  {% endtablerow %}
</table>
Output
<table>
  <tr class="row1">
    <td class="col1">Cool Shirt</td>
    <td class="col2">Alien Poster</td>
    <td class="col3">Batman Poster</td>
    <td class="col4">Bullseye Shirt</td>
    <td class="col5">Another Classic Vinyl</td>
    <td class="col6">Awesome Jeans</td>
  </tr>
</table>

tablerow tag parameters

cols

Defines how many columns the tables should have.

Input
<table>
  {% tablerow product in collection.products cols:2 %}
    {{ product.title }}
  {% endtablerow %}
</table>
Output
<table>
  <tr class="row1">
    <td class="col1">Cool Shirt</td>
    <td class="col2">Alien Poster</td>
  </tr>
  <tr class="row2">
    <td class="col1">Batman Poster</td>
    <td class="col2">Bullseye Shirt</td>
  </tr>
  <tr class="row3">
    <td class="col1">Another Classic Vinyl</td>
    <td class="col2">Awesome Jeans</td>
  </tr>
</table>

limit

Exits the loop after a specific index.

{% tablerow product in collection.products cols:2 limit:3 %}
  {{ product.title }}
{% endtablerow %}

offset

Starts the loop at a specific index.

{% tablerow product in collection.products cols:2 offset:3 %}
  {{ product.title }}
{% endtablerow %}

range

Defines a range of numbers to loop through. Both literal and variable numbers can define the range.

<table>
  {% tablerow i in (3..5) %}
    {{ i }}
  {% endtablerow %}
</table>

{% assign num = 4 %}
<table>
  {% tablerow i in (1..num) %}
    {{ i }}
  {% endtablerow %}
</table>

Note

This topic is a compilation of knowledge found at Shopify Themes, Liquid Documentation, Liquid Gem Documentation, and Liquid for Designers.

Try it

Edit the input and press Run. The example runs on this site, read-only.