Guide · Shopify themes

Shopify Liquid templates —
how they actually work

Liquid is Shopify's templating language. Before writing sections or snippets, it helps to have a clear model of how a request travels from a Shopify URL to rendered HTML — and where Liquid fits in.

The render pipeline

01

Request hits a route

Shopify maps URLs to template types. /products/my-product maps to the product template type. /collections/all maps to collection. The active theme determines which file handles that type.

02

layout/theme.liquid wraps everything

Every page (except bare Ajax responses) renders inside theme.liquid. The {{ content_for_layout }} tag is where the specific template's output gets injected. Global head tags, nav, footer, scripts — they all live here.

03

The template file loads sections

JSON templates like product.json declare an ordered list of sections. Each section renders its own Liquid and CSS. The output is assembled in order and dropped into content_for_layout.

04

Sections render snippets

A section can include reusable partials via {% render 'card-product', product: product %}. Unlike include (deprecated), render creates an isolated scope — variables from the parent don't leak in unless you pass them explicitly.

Objects, filters, tags

Objects

Shopify exposes store data as Liquid objects — product, collection, shop, customer, cart. Objects have properties you access with dot notation: {{ product.title }}, {{ product.price | money }}. The full object reference is in Shopify's Liquid docs — knowing which properties exist on each object is most of the job.

Filters

Filters transform output values. | money formats an integer price in cents to the store's currency format. | img_url: '800x' returns a CDN-resized image URL. | default: 'fallback' handles nil values. Chain them: {{ product.featured_image | img_url: '600x' | img_tag }}.

Tags

Tags control logic and don't render output themselves. {% if %}, {% for %}, {% unless %}, {% assign %}, {% capture %}. Section-specific tags include {% schema %} (defines settings), {% stylesheet %} (scoped CSS), and {% javascript %} (scoped JS).

Common patterns

01

Accessing section settings

Settings defined in {% schema %} are available as section.settings.setting_id. Block settings are accessed inside a {% for block in section.blocks %} loop as block.settings.setting_id.

02

Paginating collections

Use {% paginate collection.products by 24 %} to split large collections across pages. Inside the paginate block, {{ paginate | default_pagination }} renders a basic paginator. Style it or render it manually with paginate.pages, paginate.next, paginate.previous.

03

Handling nil gracefully

Products without a featured image, collections with no description — Liquid returns nil for missing properties. Use {% if product.featured_image %} before rendering, or | default filters for inline fallbacks. Unhandled nil properties render as blank strings — silent failures that look like layout bugs.

Related