Template Tags
Including partials with render, defining section schemas, and scoped styles.
Template tags control the structure of your templates. They let you include reusable fragments, define section settings, and scope CSS and JavaScript to a single section.
layout
By default, every template uses the theme.liquid layout. To use a different layout, add a layout tag at the top of the template file:
{% layout 'checkout' %}This tells Quill to wrap the template in checkout.liquid instead of the default layout. The named layout file must exist in the layout/ folder.
To render a template with no layout at all, use none:
{% layout none %}This is useful for pages that need complete control over the HTML, such as landing pages or custom iframes.
render
Include a reusable snippet (partial template) inside your template:
{% render 'product-card' %}This inserts the content of the product-card snippet at that position.
Passing data to snippets
By default, snippets have isolated scope. They cannot access variables from the parent template. Pass data explicitly:
{% render 'product-card' with product %}The with keyword passes a single object. Inside the snippet, it is available as a variable with the same name as the snippet. In this case, the snippet receives a product-card variable, but you can rename it:
{% render 'product-card' with featured_product as product %}Now the snippet receives the value as product.
Passing named variables
Pass one or more named variables:
{% render 'product-card', product: product, show_price: true, layout: 'grid' %}Inside the snippet, product, show_price, and layout are all available.
Looping with render
Render a snippet once for each item in an array:
{% render 'product-card' for collection.products as product %}This is the same as writing a for loop around a render call. The snippet receives each item as product. Inside the snippet, forloop variables are also available.
section
Load a named section into the template:
{% section 'featured-collection' %}Sections are self-contained blocks with their own template, schema, CSS, and JavaScript. They are the building blocks of a theme. The visual editor lets merchants add, remove, and reorder sections on a page.
schema
Define the settings for a section. The visual editor reads this schema and builds a form in the sidebar:
{% schema %}
{
"name": "Hero Banner",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "Welcome"
},
{
"type": "image_picker",
"id": "background",
"label": "Background image"
},
{
"type": "select",
"id": "height",
"label": "Section height",
"options": [
{ "value": "small", "label": "Small" },
{ "value": "medium", "label": "Medium" },
{ "value": "large", "label": "Large" }
],
"default": "medium"
}
]
}
{% endschema %}Access these settings in your template with section.settings:
<div class="hero hero--{{ section.settings.height }}">
<h1>{{ section.settings.heading }}</h1>
</div>The merchant changes these values through the visual editor. No code changes are needed. See the Section Schema reference for all setting types.
include (deprecated)
The include tag was the old way to insert a snippet. It still works, but you should use render instead:
{% comment %} Old way (deprecated) {% endcomment %}
{% include 'product-card' %}
{% comment %} New way (use this) {% endcomment %}
{% render 'product-card' %}The key difference is scope. With include, the snippet can access all variables from the parent template. With render, the snippet has isolated scope and only sees data you pass to it explicitly. Isolated scope is safer and easier to reason about.
stylesheet
Add CSS that is scoped to the current section:
{% stylesheet %}
.hero {
padding: 4rem 2rem;
text-align: center;
}
.hero--small { min-height: 300px; }
.hero--medium { min-height: 500px; }
.hero--large { min-height: 700px; }
{% endstylesheet %}The CSS is automatically scoped so it only affects elements inside this section. It will not leak into other parts of the page.
javascript
Add JavaScript that is scoped to the current section:
{% javascript %}
const section = document.querySelector('[data-section-id="{{ section.id }}"]');
// Your section-specific JS here
{% endjavascript %}Section JavaScript runs only on the published storefront, not in the editor preview. It is wrapped in an isolated scope so it cannot conflict with other sections.
style
Inline a <style> block that can use Liquid variables. Unlike stylesheet, which is scoped and static, style lets you inject dynamic values from section settings:
{% style %}
.hero-{{ section.id }} {
background-color: {{ section.settings.bg_colour }};
color: {{ section.settings.text_colour }};
padding: {{ section.settings.padding }}px 0;
}
{% endstyle %}Use style when you need CSS values that come from merchant settings. Use stylesheet for static CSS that does not change per section instance.
form
Generate an HTML form with the correct action URL and hidden fields:
{% form 'product', product %}
<select name="variant_id">
{% for variant in product.variants %}
<option value="{{ variant.id }}">
{{ variant.title }} - {{ variant.price | money }}
</option>
{% endfor %}
</select>
<button type="submit">Add to Cart</button>
{% endform %}The form tag handles the form's action, method, and any required hidden fields. You only need to write the visible form elements.
Supported form types:
| Type | Purpose |
|---|---|
'product' | Add a product to the cart |
'cart' | Update the cart |
'customer_login' | Log in |
'create_customer' | Register a new account |
'customer_address' | Add or edit an address |
'contact' | Contact form |
paginate
Wrap a collection to enable pagination:
{% paginate collection.products by 24 %}
{% for product in collection.products %}
<div class="product-card">
<h3>{{ product.title }}</h3>
</div>
{% endfor %}
{{ paginate | default_pagination }}
{% endpaginate %}The by parameter sets how many items per page. The default_pagination filter renders page navigation links (Previous, 1, 2, 3, Next).
Inside a paginate block, the paginate object gives you:
| Property | Description |
|---|---|
paginate.pages | Total number of pages |
paginate.current_page | Current page number |
paginate.items | Total number of items |
paginate.page_size | Items per page |
paginate.previous | Link to the previous page (nil if on first page) |
paginate.next | Link to the next page (nil if on last page) |
paginate.parts | Array of page links for building custom pagination |