Liquid is the templating language that powers CartOS themes. It lets you display dynamic content—product names, prices, collections, customer information—within your theme's HTML. If you want to customize your theme beyond what the visual editor offers, understanding Liquid basics opens up a lot of possibilities.
What is Liquid?
Liquid is a simple, readable templating language created by Shopify and used across many e-commerce platforms including CartOS. It sits between your theme's HTML and your store's data, inserting the right content in the right places.
When a customer visits your store, CartOS processes the Liquid code, replaces it with actual data, and sends plain HTML to their browser. Customers never see Liquid—they just see the final result.
Accessing theme code
To edit Liquid, go to Online Store › Themes, click the three dots on your theme, and select Edit Code. You'll see your theme's file structure with templates, sections, snippets, and assets.
Always duplicate your theme before editing code. This gives you a backup if something goes wrong.
Liquid syntax basics
Liquid uses two types of delimiters:
Output tags: {{ }}
Double curly braces output content to the page:
{{ product.title }}
This displays the product's title. Whatever is between the braces gets replaced with actual data.
Logic tags: {% %}
Curly brace with percent signs execute logic without outputting anything directly:
{% if product.available %}
In Stock
{% else %}
Sold Out
{% endif %}
This checks a condition and displays different content based on the result.
Objects and properties
Liquid gives you access to objects representing your store data. Objects have properties you access with a dot:
{{ product.title }}
{{ product.price }}
{{ product.description }}
{{ collection.title }}
{{ shop.name }}
Common objects
product – The current product on a product page
product.title– Product nameproduct.description– Full descriptionproduct.price– Price in cents (use filters to format)product.images– Array of product imagesproduct.variants– Array of variantsproduct.available– True if any variant is in stockproduct.vendor– Product vendorproduct.type– Product typeproduct.tags– Array of tags
collection – The current collection
collection.title– Collection namecollection.description– Collection descriptioncollection.products– Array of products in the collectioncollection.products_count– Number of productscollection.image– Collection image
cart – The customer's shopping cart
cart.items– Array of items in cartcart.item_count– Number of itemscart.total_price– Cart total in cents
customer – The logged-in customer (if any)
customer.first_name– Customer's first namecustomer.email– Customer's emailcustomer.orders– Array of past orders
shop – Your store
shop.name– Store nameshop.email– Store emailshop.currency– Store currency
Filters
Filters modify output. They're applied with a pipe character:
{{ product.title | upcase }}
{{ product.price | money }}
{{ product.description | truncate: 100 }}
Common filters
Text filters
upcase– Convert to uppercasedowncase– Convert to lowercasecapitalize– Capitalize first lettertruncate: 100– Limit to 100 charactersstrip_html– Remove HTML tagsescape– Escape special charactersnewline_to_br– Convert newlines to <br> tags
Number and money filters
money– Format as currency ($10.00)money_without_currency– Format without symbol (10.00)plus: 5– Add a numberminus: 5– Subtract a numbertimes: 2– Multiplydivided_by: 2– Divideround– Round to nearest integer
Date filters
date: "%B %d, %Y"– Format date (January 15, 2025)date: "%m/%d/%y"– Format date (01/15/25)
Array filters
first– Get first itemlast– Get last itemsize– Get array lengthsort– Sort alphabeticallyreverse– Reverse orderjoin: ", "– Join array into string
Image filters
img_url: '500x'– Get image URL at specific sizeimg_url: 'master'– Get full-size image URL
You can chain multiple filters:
{{ product.title | upcase | truncate: 20 }}
Conditional logic
Use if, elsif, and else to show content conditionally:
{% if product.available %}
<button>Add to Cart</button>
{% else %}
<button disabled>Sold Out</button>
{% endif %}
Comparison operators
==– Equals!=– Not equals>– Greater than<– Less than>=– Greater than or equal<=– Less than or equalcontains– Check if string/array contains value
{% if product.price > 10000 %}
Free shipping on this item!
{% endif %}
{% if product.tags contains 'sale' %}
<span class="sale-badge">On Sale</span>
{% endif %}
Combining conditions
Use and / or to combine conditions:
{% if product.available and product.price < 5000 %}
Great deal - in stock and under $50!
{% endif %}
{% if customer or cart.item_count > 0 %}
Welcome back!
{% endif %}
Unless
unless is the opposite of if—it runs when the condition is false:
{% unless product.available %}
This product is currently unavailable.
{% endunless %}
Loops
Use for to loop through arrays:
{% for product in collection.products %}
<div class="product-card">
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</div>
{% endfor %}
Loop variables
Inside a loop, you have access to special variables:
forloop.index– Current iteration (1, 2, 3...)forloop.index0– Current iteration zero-based (0, 1, 2...)forloop.first– True on first iterationforloop.last– True on last iterationforloop.length– Total number of iterations
{% for product in collection.products %}
{% if forloop.first %}
<p>Featured:</p>
{% endif %}
<h3>{{ forloop.index }}. {{ product.title }}</h3>
{% endfor %}
Limiting loops
{% for product in collection.products limit: 4 %}
{# Only show first 4 products #}
{% endfor %}
{% for product in collection.products offset: 2 %}
{# Skip first 2 products #}
{% endfor %}
Variables
Create variables with assign:
{% assign sale_price = product.price | times: 0.8 %}
<p>Sale: {{ sale_price | money }}</p>
Use capture to assign a block of content to a variable:
{% capture product_info %}
{{ product.title }} - {{ product.price | money }}
{% endcapture %}
<meta name="description" content="{{ product_info }}">
Theme structure
CartOS themes are organized into several folders:
Layout
The layout folder contains your base templates. theme.liquid is the main layout wrapping all pages—it includes your <head>, header, footer, and a {{ content_for_layout }} tag where page content goes.
Templates
The templates folder has templates for different page types:
index.liquid– Homepageproduct.liquid– Product pagescollection.liquid– Collection pagescart.liquid– Cart pagepage.liquid– Static pagesblog.liquid– Blog listingarticle.liquid– Blog postscustomers/*.liquid– Account pages
Sections
Sections are reusable, customizable components. They can have their own settings that appear in the theme editor. The sections folder contains these modular pieces.
Snippets
Snippets are small, reusable code fragments. Include them with:
{% render 'product-card', product: product %}
This is useful for code you use in multiple places, like a product card component.
Assets
CSS, JavaScript, images, and fonts go in the assets folder. Reference them with:
{{ 'style.css' | asset_url }}
{{ 'logo.png' | asset_url }}
Common customizations
Show content based on tags
{% if product.tags contains 'new' %}
<span class="badge new">New Arrival</span>
{% endif %}
{% if product.tags contains 'bestseller' %}
<span class="badge bestseller">Best Seller</span>
{% endif %}
Show sale percentage
{% if product.compare_at_price > product.price %}
{% assign discount = product.compare_at_price | minus: product.price %}
{% assign percent = discount | times: 100.0 | divided_by: product.compare_at_price | round %}
<span class="sale-percent">{{ percent }}% off</span>
{% endif %}
Display different content for logged-in customers
{% if customer %}
<p>Welcome back, {{ customer.first_name }}!</p>
{% else %}
<p><a href="/account/login">Log in</a> for exclusive offers.</p>
{% endif %}
Limit products per row
{% for product in collection.products %}
<div class="product-card">
{{ product.title }}
</div>
{% if forloop.index | modulo: 4 == 0 %}
<div class="row-break"></div>
{% endif %}
{% endfor %}
Debugging
When things don't work, these techniques help:
Output variable contents
<pre>{{ product | json }}</pre>
This shows the raw data structure, helping you understand what properties are available.
Check if something exists
{% if product.metafields.custom.size %}
Size: {{ product.metafields.custom.size }}
{% else %}
(no size metafield)
{% endif %}
Learning more
This covers the basics, but Liquid can do much more. For deeper learning:
- Study your theme's existing code to see patterns
- Make small changes and see what happens
- Check the CartOS Liquid reference documentation for all available objects and filters
- Look at how sections define their settings schema
If you're making significant theme changes, consider hiring a developer. Small tweaks are manageable with basic Liquid knowledge, but major customizations benefit from professional expertise.