Product Page Implementation Intermediate 12 min read

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-viewed

Main 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:

  1. Media position setting (left/right)
  2. Media size setting
  3. Sticky info toggle
  4. Blocks for: title, price, variants, quantity, buy buttons, description
  5. 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

  1. Product templates are JSON in Online Store 2.0
  2. product.selected_or_first_available_variant for current variant
  3. Use product.media for all media types
  4. Break into logical sections: main, description, recommendations
  5. Use blocks for flexible product info ordering
  6. Provide sensible schema settings without overwhelming
  7. Create alternate templates for special product types
  8. 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...