
In the world of Shopify theme development, the assign
tag is the undisputed workhorse for creating variables. But when your logic gets more complex, building strings with multiple parts or generating blocks of HTML can lead to messy, hard-to-read code.
This is where the often-underestimated capture
tag shines.
While assign
is perfect for storing a single value, capture
is designed to “capture” the rendered output of an entire block of Liquid code into a variable. This simple difference unlocks a host of powerful techniques that can make your theme code cleaner, more modular, and dramatically more efficient.
Let’s move beyond the basics and explore five creative use cases for mastering the capture
tag.
Use Case 1: The Performance-Boosting Loop
This is the classic and most critical use case for capture
. As covered in our post on Liquid performance, you should never loop over the same array multiple times to build different parts of a component.
Instead, loop once and use capture
to build the HTML for each part in separate variables.
The Slow Way (Multiple Loops):
<!-- Loop 1: Build images -->
<div class="product-gallery">
{% for variant in product.variants %}
<img src="{{ variant.image.src | img_url: 'medium' }}">
{% endfor %}
</div>
<!-- Loop 2: Build swatches -->
<div class="product-swatches">
{% for variant in product.variants %}
<span class="swatch"></span>
{% endfor %}
</div>
The capture
Way (Single Loop):
{% liquid
assign gallery_images = ''
assign color_swatches = ''
%}
{% for variant in product.variants %}
{% capture gallery_images %}
{{ gallery_images }}
<img src="{{ variant.image.src | img_url: 'medium' }}">
{% endcapture %}
{% capture color_swatches %}
{{ color_swatches }}
<span class="swatch"></span>
{% endcapture %}
{% endfor %}
<div class="product-gallery">{{ gallery_images }}</div>
<div class="product-swatches">{{ color_swatches }}</div>
Why it’s better: You’ve cut your server’s workload in half by iterating over the variants
array only once, leading to a significant performance improvement on products with many variants.
Use Case 2: Creating “Slots” for Reusable Snippets
Have you ever wanted to pass a block of HTML into a snippet? With capture
, you can create a powerful “slot” pattern that makes your reusable components far more flexible.
Imagine a generic card.liquid
snippet that needs a unique header and footer for different contexts.
The Snippet (card.liquid
):
<div class="card">
<div class="card-header">
{{ header_content }}
</div>
<div class="card-body">
Main content here...
</div>
<div class="card-footer">
{{ footer_content }}
</div>
</div>
Using capture
to Fill the Slots:
{% capture card_header %}
<h2>Special Product Title</h2>
<p>With a unique subtitle</p>
{% endcapture %}
{% capture card_footer %}
<a href="#" class="btn">Buy Now</a>
<a href="#" class="link">Learn More</a>
{% endcapture %}
{% render 'card',
header_content: card_header,
footer_content: card_footer
%}
Why it’s better: Your card.liquid
snippet remains clean and generic, while you retain full control over the complex HTML that gets passed into it. This is far superior to passing a dozen individual string variables.
Use Case 3: Conditionally Building CSS Class Strings
Building a dynamic string of CSS classes can get messy with multiple if
statements. capture
allows you to build the string in a more readable and organized way.
{% capture product_card_classes %}
product-card
{% if product.available == false %}product-card--sold-out{% endif %}
{% if product.compare_at_price > product.price %}product-card--on-sale{% endif %}
{% if product.tags contains 'new' %}product-card--new-arrival{% endif %}
{% endcapture %}
<div class="{{ product_card_classes | strip | strip_newlines | replace: ' ', ' ' }}">
...
</div>
Why it’s better: The logic for adding each class is cleanly separated onto its own line, making it much easier to read and maintain than a series of chained append
filters.
Use Case 4: Generating Complex JSON-LD for SEO
Writing JSON-LD for rich snippets can be a nightmare of escaped quotes and complex string concatenation. capture
simplifies this by allowing you to write the JSON structure in a more natural way.
{% capture product_json_ld %}
{
"@context": "https://schema.org/",
"@type": "Product",
"name": "{{ product.title | escape }}",
"image": "{{ product.featured_image | img_url: '1024x1024' }}",
"description": "{{ product.description | strip_html | escape }}",
"sku": "{{ product.selected_or_first_available_variant.sku }}",
"offers": {
"@type": "Offer",
"url": "{{ request.origin }}{{ product.url }}",
"priceCurrency": "{{ cart.currency.iso_code }}",
"price": "{{ product.price | money_without_currency | replace: ',', '' }}"
}
}
{% endcapture %}
<script type="application/ld+json">
{{ product_json_ld }}
</script>
Why it’s better: You can write the JSON object with proper formatting and indentation, making it vastly easier to debug than a single, long, concatenated string.
Use Case 5: Creating Fallback Chains for Content
Sometimes you want to display a piece of content from one of several possible sources. capture
combined with strip
provides an elegant way to handle this.
{% capture image_alt_text %}
{{ product.featured_image.alt | strip }}
{% endcapture %}
{% if image_alt_text == blank %}
{% capture image_alt_text %}
{{ product.title | strip }}
{% endcapture %}
{% endif %}
<img src="..." alt="{{ image_alt_text }}">
Why it’s better: This pattern is cleaner than nested if/else
statements and ensures you always have a non-empty value for critical attributes like alt
text.
Final Thoughts: A Powerful Tool for Clean Code
The capture
tag is more than just a multi-line assign
. It’s a powerful tool for improving performance, creating flexible components, and writing cleaner, more maintainable Liquid code.
The next time you find yourself building a complex string or block of HTML, reach for capture
. It will transform the way you write Liquid.
❓ What’s your favorite creative use for the capture
tag?