Error Pages and Fallbacks
Learn how to create helpful 404 pages, password pages, and graceful fallbacks that guide users back to useful content.
Error pages are inevitable. Products get discontinued, URLs get mistyped, and pages get deleted. A well-designed error page turns a frustrating dead end into an opportunity to guide visitors back to your store’s content.
The 404 Template
The 404.json template renders when a visitor reaches a URL that doesn’t exist:
{ "sections": { "main": { "type": "main-404", "settings": { "heading": "Page not found", "text": "The page you're looking for doesn't exist or has been moved." } } }, "order": ["main"]}A Basic 404 Section
{# sections/main-404.liquid #}<div class="page-404"> <h1>{{ section.settings.heading }}</h1> <p>{{ section.settings.text }}</p>
<a href="{{ routes.root_url }}" class="button"> Return to homepage </a></div>
{% schema %}{ "name": "404 Page", "settings": [ { "type": "text", "id": "heading", "label": "Heading", "default": "Page not found" }, { "type": "textarea", "id": "text", "label": "Text", "default": "The page you're looking for doesn't exist." } ]}{% endschema %}Making 404 Pages Helpful
A good 404 page does more than apologize. It helps visitors find what they need.
Include Search
<div class="page-404"> <h1>{{ section.settings.heading }}</h1> <p>{{ section.settings.text }}</p>
<div class="search-form-wrapper"> <p>Try searching for what you're looking for:</p> <form action="{{ routes.search_url }}" method="get"> <input type="search" name="q" placeholder="Search our store..." aria-label="Search" > <button type="submit">Search</button> </form> </div>
<a href="{{ routes.root_url }}" class="button"> Return to homepage </a></div>Show Popular Collections
<div class="page-404"> <h1>{{ section.settings.heading }}</h1> <p>{{ section.settings.text }}</p>
{%- if section.settings.show_collections -%} <div class="popular-collections"> <h2>Browse our collections</h2> <ul> {%- for collection in collections limit: 6 -%} {%- if collection.all_products_count > 0 -%} <li> <a href="{{ collection.url }}">{{ collection.title }}</a> </li> {%- endif -%} {%- endfor -%} </ul> </div> {%- endif -%}</div>
{% schema %}{ "name": "404 Page", "settings": [ { "type": "text", "id": "heading", "label": "Heading", "default": "Page not found" }, { "type": "textarea", "id": "text", "label": "Text" }, { "type": "checkbox", "id": "show_collections", "label": "Show popular collections", "default": true } ]}{% endschema %}Featured Products on 404
{%- if section.settings.collection != blank -%} <div class="featured-on-404"> <h2>{{ section.settings.featured_heading }}</h2> <div class="product-grid"> {%- for product in section.settings.collection.products limit: 4 -%} {% render 'product-card', product: product %} {%- endfor -%} </div> </div>{%- endif -%}
{% schema %}{ "name": "404 Page", "settings": [ { "type": "text", "id": "heading", "label": "Heading", "default": "Page not found" }, { "type": "collection", "id": "collection", "label": "Featured collection" }, { "type": "text", "id": "featured_heading", "label": "Featured section heading", "default": "You might like these" } ]}{% endschema %}The Password Page
When a store is password-protected (common before launch), visitors see the password page. This uses its own layout and template.
Password Layout
{# layout/password.liquid #}<!DOCTYPE html><html lang="{{ request.locale.iso_code }}"><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{{ shop.name }}</title>
{{ content_for_header }} {{ 'password.css' | asset_url | stylesheet_tag }}</head><body class="password-page"> {{ content_for_layout }}</body></html>Password Template
{ "sections": { "main": { "type": "main-password", "settings": {} } }, "order": ["main"]}Password Section
{# sections/main-password.liquid #}<div class="password-container"> <header class="password-header"> {%- if section.settings.logo != blank -%} <img src="{{ section.settings.logo | image_url: width: 200 }}" alt="{{ shop.name }}" class="password-logo" > {%- else -%} <h1>{{ shop.name }}</h1> {%- endif -%} </header>
<main class="password-main"> <h2>{{ section.settings.heading }}</h2> <p>{{ section.settings.text }}</p>
{%- form 'storefront_password' -%} <div class="password-form"> <label for="password" class="visually-hidden">Password</label> <input type="password" name="password" id="password" placeholder="Enter password" aria-describedby="password-error" > <button type="submit">Enter</button> </div>
{%- if form.errors -%} <p id="password-error" class="error"> {{ form.errors.messages.password }} </p> {%- endif -%} {%- endform -%}
{%- if section.settings.show_newsletter -%} <div class="password-newsletter"> <p>{{ section.settings.newsletter_text }}</p> {% render 'newsletter-form' %} </div> {%- endif -%} </main>
<footer class="password-footer"> <p> Store owner? <a href="/admin">Log in here</a> </p> </footer></div>
{% schema %}{ "name": "Password Page", "settings": [ { "type": "image_picker", "id": "logo", "label": "Logo" }, { "type": "text", "id": "heading", "label": "Heading", "default": "Opening soon" }, { "type": "richtext", "id": "text", "label": "Text", "default": "<p>Our store is getting ready. Enter the password to access.</p>" }, { "type": "checkbox", "id": "show_newsletter", "label": "Show newsletter signup", "default": true }, { "type": "text", "id": "newsletter_text", "label": "Newsletter text", "default": "Be the first to know when we launch:" } ]}{% endschema %}Graceful Fallbacks in Templates
Beyond dedicated error pages, build fallbacks into your regular templates.
Empty Collection
{%- if collection.products.size > 0 -%} <div class="product-grid"> {%- for product in collection.products -%} {% render 'product-card', product: product %} {%- endfor -%} </div>{%- else -%} <div class="collection-empty"> <h2>No products found</h2> <p>This collection doesn't have any products yet.</p> <a href="{{ routes.all_products_url }}" class="button"> Browse all products </a> </div>{%- endif -%}Empty Search Results
{%- if search.performed -%} {%- if search.results_count > 0 -%} <p>{{ search.results_count }} results for "{{ search.terms }}"</p> {%- for result in search.results -%} {% render 'search-result', result: result %} {%- endfor -%} {%- else -%} <div class="search-no-results"> <h2>No results found</h2> <p>We couldn't find anything for "{{ search.terms }}"</p>
<h3>Search tips:</h3> <ul> <li>Check your spelling</li> <li>Try more general terms</li> <li>Try different keywords</li> </ul>
<h3>Popular searches:</h3> <ul> {%- for collection in collections limit: 4 -%} <li> <a href="{{ routes.search_url }}?q={{ collection.title | url_encode }}"> {{ collection.title }} </a> </li> {%- endfor -%} </ul> </div> {%- endif -%}{%- endif -%}Empty Cart
{%- if cart.item_count > 0 -%} {# Cart contents #}{%- else -%} <div class="cart-empty"> <h2>Your cart is empty</h2> <p>Looks like you haven't added anything yet.</p> <a href="{{ routes.all_products_url }}" class="button"> Start shopping </a>
{%- if section.settings.empty_cart_collection != blank -%} <div class="cart-empty-suggestions"> <h3>Popular items</h3> <div class="product-grid"> {%- for product in section.settings.empty_cart_collection.products limit: 4 -%} {% render 'product-card', product: product %} {%- endfor -%} </div> </div> {%- endif -%} </div>{%- endif -%}Missing Images
{%- if product.featured_image -%} <img src="{{ product.featured_image | image_url: width: 400 }}" alt="{{ product.featured_image.alt | escape }}" >{%- else -%} <div class="placeholder-image"> {{ 'product-1' | placeholder_svg_tag: 'placeholder-svg' }} </div>{%- endif -%}Shopify provides placeholder SVGs for design mode:
{{ 'collection-1' | placeholder_svg_tag }}{{ 'product-1' | placeholder_svg_tag }}{{ 'image' | placeholder_svg_tag }}Redirects vs 404 Pages
Sometimes a redirect is better than a 404:
When to Use Redirects
- Product renamed (old handle → new handle)
- Page moved to new URL
- Collection restructured
- Seasonal pages that should point to new version
Redirects are managed in Shopify Admin under Online Store → Navigation → URL Redirects.
When 404 is Appropriate
- Content permanently removed
- Mistyped URLs
- Old promotional pages with no replacement
- External links to non-existent pages
Testing Error Pages
Test Your 404 Page
Visit a URL that doesn’t exist:
https://your-store.myshopify.com/this-page-does-not-existTest Password Page
- Go to Online Store → Preferences
- Enable password protection
- View your store as a visitor
Test Empty States
- Create an empty collection
- Search for gibberish
- Empty your cart
Practice Exercise
Create a comprehensive 404 page that includes:
- A friendly error message
- A search form
- Links to main collections
- Featured products
{# sections/main-404.liquid #}<div class="page-404"> <div class="page-404__content"> <h1>{{ section.settings.heading }}</h1> <p class="page-404__message">{{ section.settings.text }}</p>
<form action="{{ routes.search_url }}" method="get" class="page-404__search"> <label for="search-404" class="visually-hidden">Search</label> <input type="search" id="search-404" name="q" placeholder="Search our store..." > <button type="submit">Search</button> </form>
<a href="{{ routes.root_url }}" class="button button--primary"> Go to homepage </a> </div>
{%- if section.settings.show_collections -%} <div class="page-404__collections"> <h2>Browse collections</h2> <ul class="collection-list"> {%- for collection in collections limit: 6 -%} {%- if collection.all_products_count > 0 -%} <li> <a href="{{ collection.url }}"> {%- if collection.image -%} <img src="{{ collection.image | image_url: width: 100 }}" alt=""> {%- endif -%} <span>{{ collection.title }}</span> </a> </li> {%- endif -%} {%- endfor -%} </ul> </div> {%- endif -%}
{%- if section.settings.featured_collection != blank -%} <div class="page-404__featured"> <h2>{{ section.settings.featured_heading }}</h2> <div class="product-grid product-grid--4"> {%- for product in section.settings.featured_collection.products limit: 4 -%} {% render 'product-card', product: product %} {%- endfor -%} </div> </div> {%- endif -%}</div>
{% schema %}{ "name": "404 Page", "settings": [ { "type": "text", "id": "heading", "label": "Heading", "default": "Page not found" }, { "type": "textarea", "id": "text", "label": "Message", "default": "Sorry, the page you're looking for doesn't exist or has been moved." }, { "type": "checkbox", "id": "show_collections", "label": "Show collections", "default": true }, { "type": "collection", "id": "featured_collection", "label": "Featured collection" }, { "type": "text", "id": "featured_heading", "label": "Featured heading", "default": "Popular products" } ]}{% endschema %}Key Takeaways
- 404 pages should help visitors, not just apologize
- Include search on error pages to help visitors find content
- Show collections or products to keep visitors engaged
- Password pages use their own layout (
password.liquid) - Build fallbacks into all templates for empty states
- Use placeholders in design mode for empty content
- Prefer redirects when content has moved, 404 when removed
What’s Next?
Congratulations! You’ve completed Module 4: Templates, Layouts, and the Rendering Pipeline. You now understand how Shopify assembles pages from layouts, templates, and sections.
In the next module, you’ll dive deep into Sections, Blocks, and Schema Design to build powerful, customizable theme components.
Finished this lesson?
Mark it complete to track your progress.
Discussion
Loading comments...