Shopify Liquid Themes
Theme Architecture
.
βββ sections/ # Full-width page modules with {% schema %} β hero, product grid, testimonials
βββ blocks/ # Nestable components with {% schema %} β slides, feature items, text blocks
βββ snippets/ # Reusable fragments via {% render %} β buttons, icons, image helpers
βββ layout/ # Page wrappers (must include {{ content_for_header }} and {{ content_for_layout }})
βββ templates/ # JSON files defining which sections appear on each page type
βββ config/ # Global theme settings (settings_schema.json, settings_data.json)
βββ locales/ # Translation files (en.default.json, fr.json, etc.)
βββ assets/ # Static CSS, JS, images (prefer {% stylesheet %}/{% javascript %} instead)
When to use what
| Need |
Use |
Why |
| Full-width customizable module |
Section |
Has {% schema %}, appears in editor, renders blocks |
| Small nestable component with editor settings |
Block |
Has {% schema %}, can nest inside sections/blocks |
| Reusable logic, not editable by merchant |
Snippet |
No schema, rendered via {% render %}, takes params |
| Logic shared across blocks/snippets |
Snippet |
Blocks can't {% render %} other blocks |
Liquid Syntax
Delimiters
{{ ... }} β Output (prints a value)
{{- ... -}} β Output with whitespace trimming
{% ... %} β Logic tag (if, for, assign) β prints nothing
{%- ... -%} β Logic tag with whitespace trimming
Operators
Comparison: ==, !=, >, <, >=, <=
Logical: and, or, contains
Critical Gotchas
- No parentheses in conditions β use nested
{% if %} instead
- No ternary β always use
{% if cond %}value{% else %}other{% endif %}
for loops max 50 iterations β use {% paginate %} for larger arrays
contains only works with strings β can't check objects in arrays
{% stylesheet %}/{% javascript %} don't render Liquid β no Liquid inside them
- Snippets can't access outer-scope variables β pass them as render params
include is deprecated β always use {% render 'snippet_name' %}
{% liquid %} tag β multi-line logic without delimiters; use echo for output
Variables
{% assign my_var = 'value' %}
{% capture my_var %}computed {{ value }}{% endcapture %}
{% increment counter %}
{% decrement counter %}
Filter Quick Reference
Filters are chained with |. Output type of one filter feeds input of next.
Array: compact, concat, find, find_index, first, has, join, last, map, reject, reverse, size, sort, sort_natural, sum, uniq, where
String: append, capitalize, downcase, escape, handleize, lstrip, newline_to_br, prepend, remove, replace, rstrip, slice, split, strip, strip_html, truncate, truncatewords, upcase, url_decode, url_encode
Math: abs, at_least, at_most, ceil, divided_by, floor, minus, modulo, plus, round, times
Money: money, money_with_currency, money_without_currency, money_without_trailing_zeros
Color: color_brightness, color_darken, color_lighten, color_mix, color_modify, color_saturate, color_desaturate, color_to_hex, color_to_hsl, color_to_rgb
Media: image_url, image_tag, video_tag, external_video_tag, media_tag, model_viewer_tag
URL: asset_url, asset_img_url, file_url, shopify_asset_url
HTML: link_to, script_tag, stylesheet_tag, time_tag, placeholder_svg_tag
Localization: t (translate), format_address, currency_selector
Other: date, default, json, structured_data, font_face, font_url, payment_button
Full details: language filters, HTML/media filters, commerce filters
Tags Quick Reference
| Category |
Tags |
| Theme |
content_for, layout, section, sections, schema, stylesheet, javascript, style |
| Control |
if, elsif, else, unless, case, when |
| Iteration |
for, break, continue, cycle, tablerow, paginate |
| Variable |
assign, capture, increment, decrement, echo |
| HTML |
form, render, raw, comment, liquid |
| Documentation |
doc |
Full details with syntax and parameters: references/tags.md
Objects Quick Reference
Global objects (available everywhere)
cart, collections, customer, localization, pages, request, routes, settings, shop, template, theme, linklists, images, blogs, articles, all_products, metaobjects, canonical_url, content_for_header, content_for_layout, page_title, page_description, handle, current_page
Page-specific objects
| Template |
Objects |
/product |
product, remote_product |
/collection |
collection, current_tags |
/cart |
cart |
/article |
article, blog |
/blog |
blog, current_tags |
/page |
page |
/search |
search |
/customers/* |
customer, order |
Full reference: commerce objects, content objects, tier 2, tier 3
Schema Tag
Sections and blocks require {% schema %} with a valid JSON object. Sections use section.settings.*, blocks use block.settings.*.
Section schema structure
{
"name": "t:sections.hero.name",
"tag": "section",
"class": "hero-section",
"limit": 1,
"settings": [],
"max_blocks": 16,
"blocks": [{ "type": "@theme" }],
"presets": [{ "name": "t:sections.hero.name" }],
"enabled_on": { "templates": ["index"] },
"disabled_on": { "templates": ["password"] }
}
Block schema structure
{
"name": "t:blocks.slide.name",
"tag": "div",
"class": "slide",
"settings": [],
"blocks": [{ "type": "@theme" }],
"presets": [{ "name": "t:blocks.slide.name" }]
}
Setting type decision table
| Need |
Setting Type |
Key Fields |
| On/off toggle |
checkbox |
default: true/false |
| Short text |
text |
placeholder |
| Long text |
textarea |
placeholder |
Rich text (with <p>) |
richtext |
β |
Inline rich text (no <p>) |
inline_richtext |
β |
| Number input |
number |
placeholder |
| Slider |
range |
min, max, default (all required), step, unit |
| Dropdown/segmented |
select |
options: [{value, label}] |
| Radio buttons |
radio |
options: [{value, label}] |
| Text alignment |
text_alignment |
default: "left"/"center"/"right" |
| Color picker |
color |
default: "#000000" |
| Image upload |
image_picker |
β |
| Video upload |
video |
β |
| External video URL |
video_url |
accept: ["youtube", "vimeo"] |
| Product picker |
product |
β |
| Collection picker |
collection |
β |
| Page picker |
page |
β |
| Blog picker |
blog |
β |
| Article picker |
article |
β |
| URL entry |
url |
β |
| Menu picker |
link_list |
β |
| Font picker |
font_picker |
default (required) |
| Editor header |
header |
content (no id needed) |
| Editor description |
paragraph |
content (no id needed) |
visible_if pattern
{
"visible_if": "{{ block.settings.layout == 'vertical' }}",
"type": "select",
"id": "alignment",
"label": "t:labels.alignment",
"options": [...]
}
Conditionally shows/hides a setting in the editor based on other setting values.
Block entry types
{ "type": "@theme" } β Accept any theme block
{ "type": "@app" } β Accept app blocks
{ "type": "slide" } β Accept only the slide block type
Full schema details and all 33 setting types: references/schema-and-settings.md
CSS & JavaScript
Per-component styles and scripts
Use {% stylesheet %} and {% javascript %} in sections, blocks, and snippets:
{% stylesheet %}
.my-component { display: flex; }
{% endstylesheet %}
{% javascript %}
console.log('loaded');
{% endjavascript %}
- One tag each per file β multiple
{% stylesheet %} tags will error
- No Liquid inside β these tags don't process Liquid; use CSS variables or classes instead
- Only supported in
sections/, blocks/, and snippets/
{% style %} tag (Liquid-aware CSS)
For dynamic CSS that needs Liquid (e.g., color settings that live-update in editor):
{% style %}
.section-{{ section.id }} {
background: {{ section.