Shopify Liquid If/Else & Loops — Conditionals Guide
Control flow is at the heart of every Shopify theme. This guide covers every conditional and looping construct available in Liquid, from simple if checks to advanced for loop patterns. Each section includes real-world Shopify examples you can copy straight into your theme files. New to Liquid? Read our beginner's guide first. For data transformation, see the complete filters reference.
Conditionals
Conditionals let you display content only when certain conditions are met. Liquid provides several conditional tags that mirror constructs found in most programming languages.
if / elsif / else / endif
The if tag evaluates an expression and renders its body when the expression is truthy. You can chain additional conditions with elsif and provide a fallback with else. Every if block must end with endif.
A basic if statement:
{% if product.available %}
<span class="badge">In Stock</span>
{% endif %}Using elsif and else for multiple branches:
{% if product.available %}
<button>Add to Cart</button>
{% elsif product.selected_or_first_available_variant.available %}
<button>Select Available Variant</button>
{% else %}
<button disabled>Sold Out</button>
{% endif %}Truthiness in Liquid: In Liquid, nil and false are the only falsy values. Empty strings (""), the number 0, and empty arrays are all truthy. To check for an empty string, use the blank keyword:
{% if product.metafields.custom.subtitle != blank %}
<p class="subtitle">{{ product.metafields.custom.subtitle }}</p>
{% endif %}Comparison Operators
Liquid supports the standard comparison operators for building conditional expressions.
Equal (==) and Not Equal (!=):
{%- comment -%} Equal {%- endcomment -%}
{% if template.name == "product" %}
<!-- We are on a product page -->
{% endif %}
{%- comment -%} Not equal {%- endcomment -%}
{% if cart.item_count != 0 %}
<a href="/cart">View Cart ({{ cart.item_count }})</a>
{% endif %}Greater Than, Less Than, and Variants (>, <, >=, <=):
{% if product.price > 10000 %}
<span class="premium-badge">Premium</span>
{% endif %}
{% if product.compare_at_price > product.price %}
<span class="sale-badge">On Sale</span>
{% endif %}
{% if cart.total_price >= 5000 %}
<p>You qualify for free shipping!</p>
{% endif %}contains: Works with strings (checks for a substring) and arrays (checks for membership).
{%- comment -%} String contains {%- endcomment -%}
{% if product.title contains "Gift" %}
<span>🎁 Gift-ready</span>
{% endif %}
{%- comment -%} Array contains {%- endcomment -%}
{% if product.tags contains "new-arrival" %}
<span class="badge badge--new">New</span>
{% endif %}Logical Operators
Combine multiple conditions using and and or. Liquid evaluates these operators from right to left and does not support parenthetical grouping, so keep expressions simple or split them into nested if blocks.
{%- comment -%} Using "and" — both conditions must be true {%- endcomment -%}
{% if product.available and product.price < 5000 %}
<p>In stock and under $50!</p>
{% endif %}
{%- comment -%} Using "or" — at least one condition must be true {%- endcomment -%}
{% if template.name == "product" or template.name == "collection" %}
<!-- Show product-related sidebar -->
{% endif %}A real-world example combining price and availability:
{% if product.available and product.compare_at_price > product.price %}
<div class="promo-banner">
<p>
Save {{ product.compare_at_price | minus: product.price | money }} on
{{ product.title }} — limited time!
</p>
</div>
{% endif %}unless
unless is the inverse of if. The block renders when the condition is falsy. It reads more naturally in certain situations, for example “unless the product is available, show sold out.”
{% unless product.available %}
<div class="sold-out-overlay">
<p>Sold Out</p>
</div>
{% endunless %}Use unless when the negative condition reads more naturally. If you find yourself writing unless ... else, switch to if ... else for clarity.
{%- comment -%}
Hide prices if customer tag "wholesale" is NOT present.
"unless" reads naturally here.
{%- endcomment -%}
{% unless customer.tags contains "wholesale" %}
<p class="price">{{ product.price | money }}</p>
{% endunless %}case / when
case / when is Liquid's switch statement. It compares a variable against multiple values and runs the matching block. Use it when you have many equality checks against the same variable.
{% case template.name %}
{% when "index" %}
{{ "hero-banner.liquid" | render }}
{% when "product" %}
{{ "product-details.liquid" | render }}
{% when "collection" %}
{{ "collection-grid.liquid" | render }}
{% else %}
{{ "default-content.liquid" | render }}
{% endcase %}You can also match multiple values in a single when by separating them with or:
{% case product.type %}
{% when "T-Shirt" or "Hoodie" or "Jacket" %}
<p>Check our <a href="/pages/size-guide">size guide</a>.</p>
{% when "Gift Card" %}
<p>Gift cards are delivered by email.</p>
{% else %}
<p>Contact us for sizing questions.</p>
{% endcase %}Loops
Loops let you iterate over arrays and ranges to render repeated content. The for tag is the workhorse, while tablerow is a specialized helper for HTML tables.
for Loop Basics
Use for to iterate over any array. The most common use in Shopify themes is looping through products in a collection or images on a product.
Looping through products in a collection:
<div class="product-grid">
{% for product in collection.products %}
<div class="product-card">
<img
src="{{ product.featured_image | image_url: width: 400 }}"
alt="{{ product.featured_image.alt | escape }}"
>
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</div>
{% endfor %}
</div>Looping through product images:
<div class="product-gallery">
{% for image in product.images %}
<img
src="{{ image | image_url: width: 600 }}"
alt="{{ image.alt | escape }}"
loading="lazy"
>
{% endfor %}
</div>The forloop Object
Inside every for loop, Liquid exposes a forloop object with useful properties:
forloop.index— current iteration (1-based)forloop.index0— current iteration (0-based)forloop.first—trueon the first iterationforloop.last—trueon the last iterationforloop.length— total number of iterationsforloop.rindex— iterations remaining (1-based)forloop.rindex0— iterations remaining (0-based)
<ul class="product-list">
{% for product in collection.products %}
<li class="
product-list__item
{% if forloop.first %}product-list__item--first{% endif %}
{% if forloop.last %}product-list__item--last{% endif %}
">
<span class="index">{{ forloop.index }} of {{ forloop.length }}</span>
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</li>
{% endfor %}
</ul>for Loop Parameters
Liquid's for tag accepts several parameters to control iteration:
limit— maximum number of iterationsoffset— number of items to skipreversed— iterate in reverse order
Show the first 4 products in a collection:
<div class="featured-products">
{% for product in collection.products limit: 4 %}
<div class="product-card">
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</div>
{% endfor %}
</div>Skip the first 2 products and show the next 4:
{% for product in collection.products limit: 4 offset: 2 %}
<p>{{ product.title }}</p>
{% endfor %}Iterate in reverse order:
{%- comment -%} Show products newest-first (reversed) {%- endcomment -%}
{% for product in collection.products reversed %}
<p>{{ product.title }}</p>
{% endfor %}You can also loop over a numeric range using (1..5):
{%- comment -%} Render 5 star icons {%- endcomment -%}
<div class="stars">
{% for i in (1..5) %}
{% if i <= product.metafields.reviews.rating %}
<span class="star star--filled">★</span>
{% else %}
<span class="star star--empty">☆</span>
{% endif %}
{% endfor %}
</div>break and continue
break exits the loop entirely, while continue skips the current iteration and moves to the next one.
Use break to stop once you find what you need:
{%- comment -%} Find the first available variant and stop {%- endcomment -%}
{% for variant in product.variants %}
{% if variant.available %}
<p>First available: {{ variant.title }} — {{ variant.price | money }}</p>
{% break %}
{% endif %}
{% endfor %}Use continue to skip items that do not match your criteria:
{%- comment -%} Only show products priced above $20 {%- endcomment -%}
{% for product in collection.products %}
{% if product.price < 2000 %}
{% continue %}
{% endif %}
<div class="product-card">
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</div>
{% endfor %}tablerow
The tablerow tag automatically generates HTML <tr> and <td> elements. You only need to wrap it in a <table>. Use the cols parameter to set the number of columns.
<table class="product-table">
{% tablerow product in collection.products cols: 3 %}
<div class="product-cell">
<img
src="{{ product.featured_image | image_url: width: 300 }}"
alt="{{ product.featured_image.alt | escape }}"
>
<p>{{ product.title }}</p>
<p>{{ product.price | money }}</p>
</div>
{% endtablerow %}
</table>Inside a tablerow, you also have access to a tablerowloop object with the same properties as forloop (index, index0, first, last, length, col, col0, col_first, col_last, row):
<table>
{% tablerow product in collection.products cols: 4 %}
<div class="
cell
{% if tablerowloop.col_first %}cell--row-start{% endif %}
{% if tablerowloop.col_last %}cell--row-end{% endif %}
">
<p>Item {{ tablerowloop.index }} (Row {{ tablerowloop.row }}, Col {{ tablerowloop.col }})</p>
<h4>{{ product.title }}</h4>
</div>
{% endtablerow %}
</table>else in for (Empty Collections)
You can add an else block inside a for loop. It renders when the array is empty or nil. This is invaluable for gracefully handling empty collections and search results.
{% for product in collection.products %}
<div class="product-card">
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</div>
{% else %}
<p class="empty-state">
No products found in this collection.
<a href="/collections/all">Browse all products</a>.
</p>
{% endfor %}Handling empty search results:
{% for item in search.results %}
<div class="search-result">
<h3>
<a href="{{ item.url }}">{{ item.title }}</a>
</h3>
<p>{{ item.content | strip_html | truncatewords: 30 }}</p>
</div>
{% else %}
<p>No results found for “{{ search.terms }}”. Try a different search term.</p>
{% endfor %}Practical Patterns
Now that you know the building blocks, here are common patterns that combine conditionals and loops in real Shopify themes.
Pagination
Shopify collections can contain hundreds of products. The paginate tag splits them into pages. Wrap your for loop inside a paginate block and render navigation links using the paginate object.
{% paginate collection.products by 12 %}
<div class="product-grid">
{% for product in collection.products %}
<div class="product-card">
<img
src="{{ product.featured_image | image_url: width: 400 }}"
alt="{{ product.featured_image.alt | escape }}"
>
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</div>
{% endfor %}
</div>
{%- comment -%} Pagination navigation {%- endcomment -%}
{% if paginate.pages > 1 %}
<nav class="pagination">
{% if paginate.previous %}
<a href="{{ paginate.previous.url }}">« Previous</a>
{% endif %}
{% for part in paginate.parts %}
{% if part.is_link %}
<a href="{{ part.url }}">{{ part.title }}</a>
{% else %}
<span class="current">{{ part.title }}</span>
{% endif %}
{% endfor %}
{% if paginate.next %}
<a href="{{ paginate.next.url }}">Next »</a>
{% endif %}
</nav>
{% endif %}
{% endpaginate %}Nested Conditionals in Loops
Combining if inside for is a powerful pattern for filtering and applying conditional styling.
Filtering products by tag within a loop:
<div class="sale-products">
<h2>On Sale Now</h2>
{% for product in collection.products %}
{% if product.compare_at_price > product.price %}
<div class="product-card product-card--sale">
<span class="badge badge--sale">
Save {{ product.compare_at_price | minus: product.price | money }}
</span>
<h3>{{ product.title }}</h3>
<p>
<s>{{ product.compare_at_price | money }}</s>
{{ product.price | money }}
</p>
</div>
{% endif %}
{% endfor %}
</div>Alternating row styles using forloop.index with the modulo operator:
{% for product in collection.products %}
{% assign is_even = forloop.index | modulo: 2 %}
<div class="product-row {% if is_even == 0 %}product-row--even{% else %}product-row--odd{% endif %}">
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</div>
{% endfor %}Conditional CSS Classes
One of the most common patterns in Shopify themes is adding CSS classes conditionally. This keeps your templates clean while enabling rich visual states.
Active navigation link:
<nav>
{% for link in linklists.main-menu.links %}
<a
href="{{ link.url }}"
class="nav-link {% if link.active %}nav-link--active{% endif %} {% if link.child_active %}nav-link--child-active{% endif %}"
>
{{ link.title }}
</a>
{% endfor %}
</nav>First/last item styling and sold-out state:
<ul class="product-grid">
{% for product in collection.products %}
<li class="
product-grid__item
{% if forloop.first %} product-grid__item--first{% endif %}
{% if forloop.last %} product-grid__item--last{% endif %}
{% unless product.available %} product-grid__item--sold-out{% endunless %}
{% if product.compare_at_price > product.price %} product-grid__item--on-sale{% endif %}
">
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</li>
{% endfor %}
</ul>Grid column classes based on section settings:
{% case section.settings.columns %}
{% when 2 %}
{% assign grid_class = "grid--2-col" %}
{% when 3 %}
{% assign grid_class = "grid--3-col" %}
{% when 4 %}
{% assign grid_class = "grid--4-col" %}
{% else %}
{% assign grid_class = "grid--3-col" %}
{% endcase %}
<div class="product-grid {{ grid_class }}">
{% for product in collection.products %}
<div class="product-card">
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</div>
{% endfor %}
</div>Related Guides
Shopify Liquid Cheat Sheet
Quick-reference for tags, objects, and filters.
Shopify Liquid for Beginners
Learn what Liquid is and write your first template code.
Shopify Liquid Filters
Complete reference of string, number, array, and date filters.
Liquid Code Examples
15+ real-world Shopify Liquid code examples by use case.
We're building an AI Liquid code generator — join the waitlist.
Get early access when we launch. No spam, just one email.