Skip to content

Templates and page types

The five page types, which context keys exist on each page, and what the platform owns.

Updated 2 Sept 20262 min read
On this page

This page is for anyone writing page templates. By the end you know which file renders for which path, which context keys are present on each page, and where the theme's boundary ends.

Routing#

The request path determines the page type, and templates/<page_type>.liquid renders inside the layout:

Storefront path request.page_type Template
/stores/{slug} and /stores/{slug}?q=… home templates/home.liquid
/stores/{slug}/c/{category} category templates/category.liquid
/stores/{slug}/products/{product} product templates/product.liquid
/stores/{slug}/cart cart templates/cart.liquid

search is a reserved request.page_type value. Search today renders through templates/home.liquid with a non-empty request.search and page_sections suppressed, so the product list is the search result.

If the required template is missing or fails to render, the platform serves the default storefront page for that path instead of an error.

Context keys per page#

Key home category product cart
store, request, categories, theme_settings ✓ ✓ ✓ ✓
products, pagination ✓ ✓ — —
category — ✓ — —
product — — ✓ —
related_products — — ✓ —
product_reviews — — when published reviews exist —
cart when the shopper has a cart when the shopper has a cart when the shopper has a cart ✓
page_sections ✓ (suppressed during search) ✓ ✓ ✓

The full shape of every object is in Liquid objects. Test for optional keys with {% if product_reviews %} and the like.

What the platform owns#

  • Checkout, order confirmation and order tracking pages are platform-owned and never themed; this is a standing security decision.
  • Add to cart, quantity changes and removal happen through data-dukkan-* attributes the runtime binds; there are no custom forms that write to the cart.
  • Visitor currency conversion, the cart badge, dark mode, back to top and back-in-stock alerts are all runtime modules enabled by attributes; see Sections.

Pagination#

pagination carries page, perPage, total, totalPages, hasNextPage and hasPrevPage. Build page links by appending ?page=N to store.url or category.url, preserving q during search:

snippets/pager.liquidLiquid
{% if pagination.totalPages > 1 %}
<nav aria-label="{{ 'pager.label' | t }}">
  {% if pagination.hasPrevPage %}
  <a href="{{ pager_base }}?page={{ pagination.page | minus: 1 }}{% if request.search %}&q={{ request.search | url_encode }}{% endif %}">{{ 'pager.prev' | t }}</a>
  {% endif %}
  <span>{{ pagination.page }} / {{ pagination.totalPages }}</span>
  {% if pagination.hasNextPage %}
  <a href="{{ pager_base }}?page={{ pagination.page | plus: 1 }}{% if request.search %}&q={{ request.search | url_encode }}{% endif %}">{{ 'pager.next' | t }}</a>
  {% endif %}
</nav>
{% endif %}

Structured data#

The only exception to the <script> ban is a JSON-LD block. Use json to serialize values safely, strip_html for the description and money_amount for the price:

snippets/ld-product.liquidLiquid
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": {{ product.name | json }},
  "description": {{ product.description | strip_html | json }},
  "offers": {
    "@type": "Offer",
    "price": {{ product.price | money_amount | json }},
    "priceCurrency": {{ product.price.currency | json }},
    "availability": "{% if product.available %}https://schema.org/InStock{% else %}https://schema.org/OutOfStock{% endif %}"
  }
}
</script>