capture for building strings
{% capture product_class %}
product-card
{% if product.available %}in-stock{% else %}sold-out{% endif %}
{% if product.compare_at_price > product.price %}on-sale{% endif %}
{% endcapture %}
<div class="{{ product_class | strip | split: ' ' | join: ' ' }}">
Use capture to build class strings or HTML fragments conditionally. The filter chain removes extra whitespace from the multiline capture.
for loop with forloop object
{% for variant in product.variants %}
{% unless forloop.first %},{% endunless %}
{{ variant.title }}
{% comment %} forloop.index, forloop.last, forloop.rindex also available {% endcomment %}
{% endfor %}
assign vs capture
Use assign for simple values: {% assign price = product.price | money %}. Use capture when the value spans multiple lines or includes Liquid logic. Never use capture for simple string assignments.
section settings schema
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "Featured Products"
},
{
"type": "range",
"id": "products_count",
"min": 2, "max": 12, "step": 2,
"default": 4,
"label": "Number of products"
}
the unless alternative to if-not
Prefer {% unless condition %} over {% if condition == false %} — it's shorter, reads like English, and avoids negation errors in complex conditions.
tablerow for grid rendering
Liquid's tablerow tag renders table rows with automatic column counting: {% tablerow product in collection.products cols:3 %}. Less common now that CSS grid is standard, but useful for email templates or legacy layouts.