Shopify Liquid Fundamentals
Core Principles
Whitespace Control
-
Use {%- -%} to trim whitespace around Liquid tags
-
Prefer {% render %} over deprecated {% include %}
-
Use {% liquid %} for multi-line logic blocks
Example:
{%- liquid assign product_available = product.available | default: false assign product_price = product.price | default: 0
if product == blank assign error_message = 'Product not found' endif -%}
Performance Optimization
-
Minimize Liquid logic in templates
-
Use assign for complex calculations instead of inline logic
-
Cache expensive operations
-
Use snippets for reusable code
Good:
{%- assign discounted_price = product.price | times: 0.9 | money -%} <p>Sale: {{ discounted_price }}</p>
Bad:
<p>Sale: {{ product.price | times: 0.9 | money }}</p> <p>Save: {{ product.price | times: 0.1 | money }}</p>
Error Handling
Always provide defaults and handle empty states:
{%- liquid assign heading = section.settings.heading | default: 'Default Heading' assign show_vendor = section.settings.show_vendor | default: false
if product == blank assign error_message = 'Product unavailable' endif -%}
{%- if error_message -%} <p class="error">{{ error_message }}</p> {%- else -%} <!-- Normal content --> {%- endif -%}
Liquid Syntax Essentials
Output
{{ variable }} # Output variable {{ variable | escape }} # Escape HTML {{ variable | default: 'fallback' }} # Provide default {{- variable -}} # Trim whitespace
Logic
{% if condition %} {% elsif other_condition %} {% else %} {% endif %}
{% unless condition %} {% endunless %}
{% case variable %} {% when 'value1' %} {% when 'value2' %} {% else %} {% endcase %}
Loops
{% for item in collection %} {{ item.title }} {% endfor %}
{% for item in collection limit: 4 %} {{ item.title }} {% endfor %}
{% for i in (1..5) %} Item {{ i }} {% endfor %}
Variables
{% assign name = 'value' %} {% capture variable %} Content here {% endcapture %}
Common Filters
String Filters
{{ 'hello' | capitalize }} # Hello {{ 'HELLO' | downcase }} # hello {{ 'hello' | upcase }} # HELLO {{ 'hello world' | truncate: 8 }} # hello... {{ '<p>test</p>' | escape }} # <p">>test</p">> {{ 'hello world' | remove: 'world' }} # hello {{ 'hello' | append: ' world' }} # hello world
Array Filters
{{ collection | size }} # Number of items {{ collection | first }} # First item {{ collection | last }} # Last item {{ collection | join: ', ' }} # Join with comma {{ collection | reverse }} # Reverse order {{ collection | sort: 'title' }} # Sort by property
Math Filters
{{ 10 | plus: 5 }} # 15 {{ 10 | minus: 5 }} # 5 {{ 10 | times: 5 }} # 50 {{ 10 | divided_by: 5 }} # 2 {{ 10.5 | ceil }} # 11 {{ 10.5 | floor }} # 10 {{ 10.5 | round }} # 11
Money Filters
{{ 1000 | money }} # $10.00 {{ 1000 | money_with_currency }} # $10.00 USD {{ 1000 | money_without_currency }} # 10.00
Image Filters
{{ image | image_url: width: 400 }} {{ image | image_url: width: 400, height: 400 }} {{ image | image_tag }} {{ image | image_tag: alt: 'Description' }}
Shopify Objects
Global Objects
{{ shop.name }} # Store name {{ shop.email }} # Store email {{ cart.item_count }} # Cart items {{ customer.name }} # Customer name (if logged in) {{ settings.color_primary }} # Theme setting
Product Objects
{{ product.title }} {{ product.price }} {{ product.compare_at_price }} {{ product.available }} {{ product.vendor }} {{ product.type }} {{ product.featured_image }} {{ product.url }}
Collection Objects
{{ collection.title }} {{ collection.description }} {{ collection.products }} {{ collection.products_count }} {{ collection.url }}
Best Practices
- Always Escape User Input
<h1>{{ product.title | escape }}</h1> <p>{{ customer.name | escape }}</p>
- Provide Defaults
{%- assign heading = section.settings.heading | default: 'Default Title' -%} {%- assign image = product.featured_image | default: blank -%}
- Check for Blank Values
{%- if heading != blank -%} <h2>{{ heading | escape }}</h2> {%- endif -%}
- Use Liquid Tag for Multi-line Logic
{%- liquid assign is_sale = false if product.compare_at_price > product.price assign is_sale = true endif
assign discount_percent = product.compare_at_price | minus: product.price | times: 100 | divided_by: product.compare_at_price -%}
- Minimize Nested Conditions
{%- # Bad -%} {% if product.available %} {% if product.compare_at_price > product.price %} <span>On Sale</span> {% endif %} {% endif %}
{%- # Good -%} {%- liquid assign is_on_sale = false if product.available and product.compare_at_price > product.price assign is_on_sale = true endif -%}
{%- if is_on_sale -%} <span>On Sale</span> {%- endif -%}
Common Patterns
Conditional Class Names
<div class="product{% if product.available %} in-stock{% else %} out-of-stock{% endif %}"> <!-- Content --> </div>
Loop with Index
{%- for product in collection.products -%} <div class="product-{{ forloop.index }}"> {%- if forloop.first -%} <span>Featured</span> {%- endif -%} {{ product.title }} </div> {%- endfor -%}
Empty State Handling
{%- if collection.products.size > 0 -%} {%- for product in collection.products -%} <!-- Product markup --> {%- endfor -%} {%- else -%} <p>No products available.</p> {%- endif -%}
Responsive Images
{%- if product.featured_image -%} <img srcset=" {{ product.featured_image | image_url: width: 400 }} 400w, {{ product.featured_image | image_url: width: 800 }} 800w, {{ product.featured_image | image_url: width: 1200 }} 1200w " sizes="(min-width: 1024px) 33vw, (min-width: 768px) 50vw, 100vw" src="{{ product.featured_image | image_url: width: 800 }}" alt="{{ product.featured_image.alt | escape }}" loading="lazy" width="800" height="{{ 800 | divided_by: product.featured_image.aspect_ratio | ceil }}"
{%- endif -%}
Rendering Snippets
Basic Rendering
{% render 'product-card' %}
With Parameters
{% render 'product-card', product: product, show_vendor: true %}
With Multiple Parameters
{% render 'button', text: 'Add to Cart', url: product.url, style: 'primary', size: 'large' %}
Performance Tips
-
Limit loops - Use limit parameter when possible
-
Cache expensive operations - Assign to variables
-
Minimize API calls - Don't call same object property multiple times
-
Use snippets wisely - Balance between reusability and overhead
-
Optimize images - Use appropriate sizes with image filters
Common Mistakes to Avoid
❌ Don't repeat expensive operations:
<p>{{ product.price | times: 1.1 | money }}</p> <p>{{ product.price | times: 1.1 | money }}</p>
✅ Do assign to variable:
{%- assign price_with_tax = product.price | times: 1.1 | money -%} <p>{{ price_with_tax }}</p> <p>{{ price_with_tax }}</p>
❌ Don't forget to escape:
<h1>{{ product.title }}</h1>
✅ Do escape user input:
<h1>{{ product.title | escape }}</h1>
❌ Don't use deprecated include:
{% include 'snippet-name' %}
✅ Do use render:
{% render 'snippet-name' %}
Follow these fundamentals for clean, performant, maintainable Shopify Liquid code.