Product Page Architecture and Schema Strategy
Understand how product pages work in Shopify Online Store 2.0, including template structure, the product object, and designing flexible section schemas.
The product page is the most important page in any e-commerce store. It’s where browsing becomes buying. Understanding product page architecture helps you build flexible, high-converting product experiences.
The Product Object
When a customer visits a product URL, Shopify provides the product object:
{{ product.title }} {# "Classic T-Shirt" #}{{ product.handle }} {# "classic-t-shirt" #}{{ product.description }} {# Full HTML description #}{{ product.price }} {# Price in cents: 2999 #}{{ product.compare_at_price }} {# Original price if on sale #}{{ product.available }} {# true/false #}{{ product.vendor }} {# "Your Brand" #}{{ product.type }} {# "T-Shirts" #}{{ product.tags }} {# Array of tags #}{{ product.images }} {# Array of images #}{{ product.variants }} {# Array of variants #}{{ product.options }} {# Array of option names #}Product Template Structure
In Online Store 2.0, product templates are JSON files that reference sections:
{# templates/product.json #}{ "sections": { "main": { "type": "product-main", "settings": { "enable_sticky_info": true, "media_size": "large" } }, "description": { "type": "product-description", "settings": {} }, "recommendations": { "type": "product-recommendations", "settings": { "heading": "You may also like" } } }, "order": ["main", "description", "recommendations"]}Section Organization
Break the product page into logical sections:
Product Page├── product-main (Gallery + Form)├── product-description (Full description, specs)├── product-tabs (Details, shipping, reviews)├── product-recommendations└── recently-viewedMain Product Section
The core section with media and purchase form:
{# sections/product-main.liquid #}
<section class="product-main"> <div class="product-main__container container"> {# Media/Gallery #} <div class="product-main__media"> {% render 'product-gallery', product: product %} </div>
{# Product Info #} <div class="product-main__info"> {% render 'product-info', product: product %} {% render 'product-form', product: product %} </div> </div></section>Accessing Product Data
Selected Variant
Get the currently selected or first available variant:
{%- assign current_variant = product.selected_or_first_available_variant -%}
{{ current_variant.title }} {# "Small / Blue" #}{{ current_variant.price }} {# 2999 #}{{ current_variant.sku }} {# "TSH-SM-BLU" #}{{ current_variant.available }} {# true #}{{ current_variant.id }} {# 12345678 #}Product Options
Products can have up to 3 options (Size, Color, Material, etc.):
{%- for option in product.options_with_values -%} <div class="option"> <label>{{ option.name }}</label> {%- for value in option.values -%} {{ value }} {%- endfor -%} </div>{%- endfor -%}Product Media
Access all product media (images, videos, 3D models):
{%- for media in product.media -%} {%- case media.media_type -%} {%- when 'image' -%} <img src="{{ media | image_url: width: 800 }}" alt="{{ media.alt }}"> {%- when 'video' -%} {{ media | video_tag }} {%- when 'external_video' -%} {{ media | external_video_tag }} {%- when 'model' -%} {{ media | model_viewer_tag }} {%- endcase -%}{%- endfor -%}Section Schema Strategy
Design schemas that give merchants control without overwhelming them:
{% schema %}{ "name": "Product Main", "settings": [ { "type": "header", "content": "Layout" }, { "type": "select", "id": "media_position", "label": "Media position", "options": [ { "value": "left", "label": "Left" }, { "value": "right", "label": "Right" } ], "default": "left" }, { "type": "select", "id": "media_size", "label": "Media width", "options": [ { "value": "small", "label": "Small" }, { "value": "medium", "label": "Medium" }, { "value": "large", "label": "Large" } ], "default": "medium" }, { "type": "checkbox", "id": "enable_sticky_info", "label": "Sticky product info on scroll", "default": true }, { "type": "header", "content": "Media" }, { "type": "checkbox", "id": "enable_zoom", "label": "Enable image zoom", "default": true }, { "type": "checkbox", "id": "enable_video_autoplay", "label": "Autoplay videos", "default": false }, { "type": "header", "content": "Product info" }, { "type": "checkbox", "id": "show_vendor", "label": "Show vendor", "default": false }, { "type": "checkbox", "id": "show_sku", "label": "Show SKU", "default": false }, { "type": "checkbox", "id": "show_share", "label": "Show share buttons", "default": true } ], "blocks": [ { "type": "@app" } ]}{% endschema %}Using Blocks for Flexible Content
Let merchants customize the product info section:
{ "blocks": [ { "type": "title", "name": "Title", "limit": 1, "settings": [] }, { "type": "price", "name": "Price", "limit": 1, "settings": [ { "type": "checkbox", "id": "show_compare_price", "label": "Show compare at price", "default": true } ] }, { "type": "variant_picker", "name": "Variant picker", "limit": 1, "settings": [ { "type": "select", "id": "picker_type", "label": "Type", "options": [ { "value": "dropdown", "label": "Dropdown" }, { "value": "buttons", "label": "Buttons" } ], "default": "buttons" } ] }, { "type": "quantity", "name": "Quantity selector", "limit": 1, "settings": [] }, { "type": "buy_buttons", "name": "Buy buttons", "limit": 1, "settings": [ { "type": "checkbox", "id": "show_dynamic_checkout", "label": "Show dynamic checkout button", "default": true } ] }, { "type": "description", "name": "Description", "limit": 1, "settings": [] }, { "type": "custom_text", "name": "Custom text", "settings": [ { "type": "richtext", "id": "text", "label": "Text" } ] }, { "type": "collapsible_tab", "name": "Collapsible tab", "settings": [ { "type": "text", "id": "heading", "label": "Heading", "default": "Details" }, { "type": "richtext", "id": "content", "label": "Content" } ] } ]}Rendering Blocks
<div class="product-info"> {%- for block in section.blocks -%} {%- case block.type -%} {%- when 'title' -%} <h1 class="product-info__title" {{ block.shopify_attributes }}> {{ product.title }} </h1>
{%- when 'price' -%} <div class="product-info__price" {{ block.shopify_attributes }}> {% render 'product-price', product: product %} </div>
{%- when 'variant_picker' -%} <div class="product-info__variants" {{ block.shopify_attributes }}> {% render 'product-variant-picker', product: product, type: block.settings.picker_type %} </div>
{%- when 'quantity' -%} <div class="product-info__quantity" {{ block.shopify_attributes }}> {% render 'quantity-selector' %} </div>
{%- when 'buy_buttons' -%} <div class="product-info__buttons" {{ block.shopify_attributes }}> {% render 'product-buy-buttons', product: product, show_dynamic: block.settings.show_dynamic_checkout %} </div>
{%- when 'description' -%} {%- if product.description != blank -%} <div class="product-info__description" {{ block.shopify_attributes }}> {{ product.description }} </div> {%- endif -%}
{%- when 'collapsible_tab' -%} <details class="product-info__tab" {{ block.shopify_attributes }}> <summary>{{ block.settings.heading }}</summary> <div class="product-info__tab-content"> {{ block.settings.content }} </div> </details>
{%- when '@app' -%} {% render block %}
{%- endcase -%} {%- endfor -%}</div>Alternate Product Templates
Create specialized templates for different product types:
{# templates/product.featured.json #}{ "sections": { "hero": { "type": "product-hero", "settings": { "full_width": true } }, "story": { "type": "rich-text" }, "main": { "type": "product-main" }, "features": { "type": "product-features" }, "reviews": { "type": "product-reviews" } }, "order": ["hero", "story", "main", "features", "reviews"]}Common alternate templates:
product.gift-card.json(for gift cards)product.bundle.json(for product bundles)product.subscription.json(for subscription products)product.preorder.json(for pre-orders)
Passing Product Data to JavaScript
<script type="application/json" id="product-data"> { "id": {{ product.id }}, "title": {{ product.title | json }}, "available": {{ product.available }}, "variants": {{ product.variants | json }}, "options": {{ product.options_with_values | json }}, "featured_image": {{ product.featured_image | image_url: width: 800 | json }}, "url": {{ product.url | json }} }</script>const productData = JSON.parse( document.getElementById('product-data').textContent);Product Page Layout CSS
.product-main__container { display: grid; gap: var(--spacing-xl);}
@media (min-width: 1024px) { .product-main__container { grid-template-columns: 1fr 1fr; }
/* Media size variations */ .product-main--media-small .product-main__container { grid-template-columns: 1fr 1.5fr; }
.product-main--media-large .product-main__container { grid-template-columns: 1.5fr 1fr; }
/* Sticky info */ .product-main--sticky .product-main__info { position: sticky; top: calc(var(--header-height) + var(--spacing-lg)); align-self: start; }}
/* Media position */.product-main--media-right .product-main__media { order: 2;}
.product-main--media-right .product-main__info { order: 1;}Practice Exercise
Design a product section schema that includes:
- Media position setting (left/right)
- Media size setting
- Sticky info toggle
- Blocks for: title, price, variants, quantity, buy buttons, description
- Custom collapsible tab block
Think about:
- What settings do merchants actually need?
- What order should blocks appear by default?
- Which blocks should be limited to 1 instance?
Key Takeaways
- Product templates are JSON in Online Store 2.0
product.selected_or_first_available_variantfor current variant- Use
product.mediafor all media types - Break into logical sections: main, description, recommendations
- Use blocks for flexible product info ordering
- Provide sensible schema settings without overwhelming
- Create alternate templates for special product types
- Pass data to JS via JSON script tags
What’s Next?
With the architecture understood, the next lesson covers Media Gallery and Slider for showcasing product images and videos.
Finished this lesson?
Mark it complete to track your progress.
Discussion
Loading comments...