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 Pattern | Template | Object Available |
|---|---|---|
/ | index.json | None specific |
/products/handle | product.json | product |
/collections/handle | collection.json | collection |
/collections/handle/products/handle | product.json | product, collection |
/pages/handle | page.json | page |
/blogs/handle | blog.json | blog |
/blogs/handle/handle | article.json | article, blog |
/cart | cart.json | cart |
/search | search.json | search |
/account | customers/account.json | customer |
/account/login | customers/login.json | None |
/account/register | customers/register.json | None |
/account/orders/id | customers/order.json | order |
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:
- Assigned template: If merchant assigned
product.with-video.jsonto this product, use it - Default template: Otherwise, use
product.json - Liquid fallback: If no JSON template, use
product.liquid
For Collections:
- Assigned template: Merchant-assigned alternate template
- Default template:
collection.json - Liquid fallback:
collection.liquid
For Pages:
- Assigned template:
page.contact.jsonif assigned - Default template:
page.json - 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:
| Value | Page Type |
|---|---|
index | Homepage |
product | Product page |
collection | Collection page |
page | Static page |
blog | Blog listing |
article | Blog article |
cart | Cart page |
search | Search results |
customers/account | Account dashboard |
customers/login | Login page |
customers/register | Registration page |
customers/order | Order details |
customers/addresses | Address management |
404 | Not found |
password | Password page |
gift_card | Gift card page |
policy | Policy 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=50Access 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=productAccess in Liquid:
{{ search.terms }} {# "blue shirt" #}{{ search.types }} {# ["product"] #}{{ search.results }} {# Search results array #}Pagination
/collections/summer?page=2Access in Liquid:
{{ current_page }} {# 2 #}{{ paginate.pages }} {# Total pages #}Collection-Product URLs
Products can be accessed through collection context:
/collections/summer/products/blue-shirtThis URL:
- Renders the product template
- Makes both
productandcollectionobjects available - 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:
- Shows breadcrumb navigation
- Adapts based on page type
- 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
- URLs map to templates based on predictable patterns
- Handles are URL-safe identifiers for resources
- Template selection follows a priority order (assigned → default → fallback)
request.page_typetells you what page is being renderedtemplate.suffixidentifies alternate template variants- Use
routesinstead of hardcoding URLs - Query parameters affect sorting, filtering, and pagination
- 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...