Iterating Collections and Arrays
The {% for %} tag loops over arrays: collection.products, cart.items, product.variants, linklists, and arrays you create with assign. Each iteration exposes the current item and a forloop helper object.
Product grid with limit and empty state
{% for product in collection.products limit: 8 %}
<article class="product-card">
<a href="{{ product.url }}">
<img
src="{{ product.featured_image | image_url: width: 400 }}"
alt="{{ product.title | escape }}"
width="400"
height="400"
loading="lazy"
>
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</a>
</article>
{% else %}
<p>No products found in this collection.</p>
{% endfor %}The limit: 8 parameter caps iterations — essential for homepage featured grids. The {% else %} branch inside for runs when the array is empty, letting you show a friendly message instead of a blank section.
The forloop Object
Every for loop provides forloop with metadata about the iteration. Use it for zebra striping, first/last item styling, and progress indicators.
- forloop.index — 1-based position (1, 2, 3…)
- forloop.index0 — 0-based position (0, 1, 2…)
- forloop.first — true on the first iteration
- forloop.last — true on the final iteration
- forloop.length — total number of items in the array
- forloop.rindex — reverse index (countdown from end)
Cart table with row styling
{% for item in cart.items %}
<tr class="cart-item{% if forloop.first %} cart-item--first{% endif %}{% if forloop.last %} cart-item--last{% endif %}">
<td>{{ forloop.index }}</td>
<td>{{ item.product.title }}</td>
<td>{{ item.line_price | money }}</td>
</tr>
{% endfor %}Loop Modifiers
- limit: N — stop after N items
- offset: N — skip the first N items (pagination alternative)
- reversed — iterate from last to first
Try it Yourself
Note: Nested loops (collection × variants) multiply render cost. On large catalogs, paginate collections and avoid rendering every variant in listing pages.