Templates, Layouts, and the Rendering Pipeline Intermediate 10 min read

Routing and Template Selection

Understand how Shopify maps URLs to templates, including handle-based routing, template fallbacks, and using the request object for page detection.

When a visitor arrives at any URL in your store, Shopify needs to decide which template to render. This routing system maps URLs to templates based on patterns, handles, and merchant assignments. Understanding this process helps you build themes that work predictably.

URL Patterns and Templates

Shopify uses predictable URL patterns that map to specific templates:

URL PatternTemplateObject Available
/index.jsonNone specific
/products/handleproduct.jsonproduct
/collections/handlecollection.jsoncollection
/collections/handle/products/handleproduct.jsonproduct, collection
/pages/handlepage.jsonpage
/blogs/handleblog.jsonblog
/blogs/handle/handlearticle.jsonarticle, blog
/cartcart.jsoncart
/searchsearch.jsonsearch
/accountcustomers/account.jsoncustomer
/account/logincustomers/login.jsonNone
/account/registercustomers/register.jsonNone
/account/orders/idcustomers/order.jsonorder

Handle-Based Routing

Most resources use “handles” for their URLs. A handle is a URL-safe identifier:

  • Product “Blue T-Shirt” → handle: blue-t-shirt → URL: /products/blue-t-shirt
  • Collection “Summer Sale” → handle: summer-sale → URL: /collections/summer-sale
  • Page “About Us” → handle: about-us → URL: /pages/about-us

Shopify automatically generates handles from titles, but merchants can edit them.

Accessing Handles in Liquid

{{ product.handle }} {# "blue-t-shirt" #}
{{ collection.handle }} {# "summer-sale" #}
{{ page.handle }} {# "about-us" #}
{{ article.handle }} {# "10-tips-for-summer" #}

Building URLs

Use handles to create links:

<a href="/products/{{ product.handle }}">{{ product.title }}</a>
{# Or use the url property #}
<a href="{{ product.url }}">{{ product.title }}</a>

The url property is preferred because it handles edge cases and locale prefixes automatically.

Template Selection Order

When Shopify determines which template to use, it follows a priority order:

For Products:

  1. Assigned template: If merchant assigned product.with-video.json to this product, use it
  2. Default template: Otherwise, use product.json
  3. Liquid fallback: If no JSON template, use product.liquid

For Collections:

  1. Assigned template: Merchant-assigned alternate template
  2. Default template: collection.json
  3. Liquid fallback: collection.liquid

For Pages:

  1. Assigned template: page.contact.json if assigned
  2. Default template: page.json
  3. Liquid fallback: page.liquid

The request Object

The request object provides information about the current page request:

{{ request.host }} {# "mystore.myshopify.com" #}
{{ request.path }} {# "/products/blue-shirt" #}
{{ request.page_type }} {# "product" #}
{{ request.design_mode }} {# true if in theme editor #}
{{ request.locale }} {# Current locale object #}
{{ request.origin }} {# "https://mystore.myshopify.com" #}

request.page_type Values

This property tells you what type of page is being rendered:

ValuePage Type
indexHomepage
productProduct page
collectionCollection page
pageStatic page
blogBlog listing
articleBlog article
cartCart page
searchSearch results
customers/accountAccount dashboard
customers/loginLogin page
customers/registerRegistration page
customers/orderOrder details
customers/addressesAddress management
404Not found
passwordPassword page
gift_cardGift card page
policyPolicy pages

Using request.page_type

Conditionally load resources based on page type:

{# In theme.liquid #}
{%- case request.page_type -%}
{%- when 'product' -%}
<script src="{{ 'product.js' | asset_url }}" defer></script>
{%- when 'collection' -%}
<script src="{{ 'collection.js' | asset_url }}" defer></script>
{%- when 'cart' -%}
<script src="{{ 'cart.js' | asset_url }}" defer></script>
{%- endcase -%}

Apply page-specific body classes:

<body class="template-{{ request.page_type }}{% if customer %} logged-in{% endif %}">

The template Object

The template object provides information about the current template:

{{ template }} {# "product" or "product.with-video" #}
{{ template.name }} {# "product" #}
{{ template.suffix }} {# "with-video" or nil #}
{{ template.directory }} {# "templates" or "customers" #}

Detecting Template Variants

Check if a specific template variant is being used:

{% if template.suffix == 'with-video' %}
{# Load video player scripts #}
<script src="{{ 'video-player.js' | asset_url }}" defer></script>
{% endif %}

Template-Based Styling

<body class="template-{{ template.name }}{% if template.suffix %} template-{{ template.name }}-{{ template.suffix }}{% endif %}">

For product.with-video.json, this outputs:

<body class="template-product template-product-with-video"></body>

The routes Object

Access standard Shopify URLs without hardcoding:

{{ routes.root_url }} {# "/" #}
{{ routes.cart_url }} {# "/cart" #}
{{ routes.cart_add_url }} {# "/cart/add" #}
{{ routes.cart_change_url }} {# "/cart/change" #}
{{ routes.cart_clear_url }} {# "/cart/clear" #}
{{ routes.account_url }} {# "/account" #}
{{ routes.account_login_url }} {# "/account/login" #}
{{ routes.account_logout_url }} {# "/account/logout" #}
{{ routes.account_register_url }} {# "/account/register" #}
{{ routes.account_addresses_url }} {# "/account/addresses" #}
{{ routes.collections_url }} {# "/collections" #}
{{ routes.all_products_url }} {# "/collections/all" #}
{{ routes.search_url }} {# "/search" #}

Always use routes instead of hardcoding paths:

{# Good #}
<a href="{{ routes.cart_url }}">Cart</a>
{# Bad - breaks with locale prefixes #}
<a href="/cart">Cart</a>

Query Parameters

URLs can include query parameters that affect page behavior:

Collection Sorting and Filtering

/collections/summer?sort_by=price-ascending
/collections/summer?filter.v.price.gte=10&filter.v.price.lte=50

Access in Liquid:

{{ collection.sort_by }} {# "price-ascending" #}
{{ collection.filters }} {# Active filters array #}
{{ collection.current_type }} {# Product type filter #}
{{ collection.current_vendor }} {# Vendor filter #}

Search Parameters

/search?q=blue+shirt&type=product

Access in Liquid:

{{ search.terms }} {# "blue shirt" #}
{{ search.types }} {# ["product"] #}
{{ search.results }} {# Search results array #}

Pagination

/collections/summer?page=2

Access in Liquid:

{{ current_page }} {# 2 #}
{{ paginate.pages }} {# Total pages #}

Collection-Product URLs

Products can be accessed through collection context:

/collections/summer/products/blue-shirt

This URL:

  1. Renders the product template
  2. Makes both product and collection objects available
  3. Affects “Back to collection” navigation
{# On product page #}
{% if collection %}
<a href="{{ collection.url }}">
← Back to {{ collection.title }}
</a>
{% endif %}

Canonical URLs

Each resource has one canonical (preferred) URL:

{{ canonical_url }}

For a product, this is always /products/handle, even if accessed via /collections/x/products/handle.

Use canonical URLs for SEO:

<link rel="canonical" href="{{ canonical_url }}">

Detecting Design Mode

When merchants preview in the theme editor:

{% if request.design_mode %}
{# Show placeholder content for empty sections #}
{% if section.settings.collection == blank %}
<div class="placeholder">
<p>Select a collection in the theme editor</p>
</div>
{% endif %}
{% endif %}

This helps merchants understand what content they need to configure.

Practice Exercise

Create a snippet that:

  1. Shows breadcrumb navigation
  2. Adapts based on page type
  3. Uses proper URLs
{# snippets/breadcrumbs.liquid #}
<nav class="breadcrumbs" aria-label="Breadcrumb">
<ol>
<li>
<a href="{{ routes.root_url }}">Home</a>
</li>
{%- case request.page_type -%}
{%- when 'product' -%}
{% if collection %}
<li>
<a href="{{ collection.url }}">{{ collection.title }}</a>
</li>
{% endif %}
<li aria-current="page">{{ product.title }}</li>
{%- when 'collection' -%}
<li>
<a href="{{ routes.collections_url }}">Collections</a>
</li>
<li aria-current="page">{{ collection.title }}</li>
{%- when 'article' -%}
<li>
<a href="{{ blog.url }}">{{ blog.title }}</a>
</li>
<li aria-current="page">{{ article.title }}</li>
{%- when 'page' -%}
<li aria-current="page">{{ page.title }}</li>
{%- when 'search' -%}
<li aria-current="page">Search results</li>
{%- when 'cart' -%}
<li aria-current="page">Cart</li>
{%- endcase -%}
</ol>
</nav>

Key Takeaways

  1. URLs map to templates based on predictable patterns
  2. Handles are URL-safe identifiers for resources
  3. Template selection follows a priority order (assigned → default → fallback)
  4. request.page_type tells you what page is being rendered
  5. template.suffix identifies alternate template variants
  6. Use routes instead of hardcoding URLs
  7. Query parameters affect sorting, filtering, and pagination
  8. Collection-product URLs provide collection context on product pages

What’s Next?

The final lesson in this module covers Error Pages and Fallbacks, helping you create helpful experiences when things go wrong.

Finished this lesson?

Mark it complete to track your progress.

Discussion

Loading comments...