SEO tutorial
How to add JSON-LD structured data in Shopify Liquid: products, reviews, and breadcrumbs
Structured data tells Google exactly what your page contains - product name, price, availability, star ratings, breadcrumb trail. When implemented correctly in Liquid, it is part of your HTML on the first byte, Googlebot reads it on the first crawl, and you become eligible for rich snippets in search results. This guide covers the three most impactful schemas for Shopify stores, with copy-paste Liquid code for each.
Reading time: ~10 minutes.
1. What JSON-LD is and where it belongs
Structured data is machine-readable markup that describes the content of a page to search engines. Google supports three formats (JSON-LD, Microdata, RDFa), but recommends JSON-LD as the preferred format for all new implementations.
JSON-LD is a <script type="application/ld+json"> block containing a JSON object that follows schema.org vocabulary. Unlike Microdata, it does not require you to annotate individual HTML elements - it lives separately from your visible markup.
Google accepts JSON-LD anywhere in the page - <head> or <body>. This means there is no reason to centralise all structured data in layout/theme.liquid with conditional template checks. The cleaner approach is to put each schema where it contextually belongs: product markup inside the product template or section, breadcrumb markup inside the relevant layout, and so on. Each file is self-contained and the structured data lives next to the content it describes.
2. Product schema
The Product rich result requires at minimum: name, an Offer with price and priceCurrency, and either review or aggregateRating (for star ratings). Here is a complete base implementation:
{%- comment -%}
sections/main-product.liquid
{%- endcomment -%}
<script type="application/ld+json">
{
"@context": "https://schema.org/",
"@type": "Product",
"name": {{ product.title | json }},
"image": [
{{ product.featured_image | image_url: width: 1200 | prepend: 'https:' | json }}
],
"description": {{ product.description | strip_html | truncate: 500 | json }},
"sku": {{ product.selected_or_first_available_variant.sku | json }},
"brand": {
"@type": "Brand",
"name": {{ shop.name | json }}
},
"offers": {
"@type": "Offer",
"url": {{ canonical_url | json }},
"priceCurrency": {{ cart.currency.iso_code | json }},
"price": {{ product.selected_or_first_available_variant.price | divided_by: 100.0 }},
"availability": "https://schema.org/{% if product.available %}InStock{% else %}OutOfStock{% endif %}",
"itemCondition": "https://schema.org/NewCondition"
}
}
</script>Note the divided_by: 100.0 on the price - Shopify stores prices in cents as integers, so 1999 represents $19.99. Dividing by 100.0 (not 100) forces a decimal result.
3. Adding aggregateRating from Metafields
Extend the Product schema above with an aggregateRating property. When review data is stored in Shopify as standard Metaobjects, the aggregate rating is available in Liquid via product.metafields.reviews.rating:
{%- assign rating = product.metafields.reviews.rating.value -%}
{%- assign rating_count = product.metafields.reviews.rating_count.value -%}
<script type="application/ld+json">
{
"@context": "https://schema.org/",
"@type": "Product",
"name": {{ product.title | json }},
"image": [
{{ product.featured_image | image_url: width: 1200 | prepend: 'https:' | json }}
],
"description": {{ product.description | strip_html | truncate: 500 | json }},
"offers": {
"@type": "Offer",
"url": {{ canonical_url | json }},
"priceCurrency": {{ cart.currency.iso_code | json }},
"price": {{ product.selected_or_first_available_variant.price | divided_by: 100.0 }},
"availability": "https://schema.org/{% if product.available %}InStock{% else %}OutOfStock{% endif %}"
}
{%- if rating != blank -%}
,"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "{{ rating }}",
"bestRating": "5",
"worstRating": "1",
"reviewCount": "{{ rating_count }}"
}
{%- endif -%}
}
</script>The if rating != blank guard ensures the aggregateRating property is only output when review data actually exists - a product with no reviews outputs valid Product schema without any rating properties, which is correct per Google's spec.
product.metafields.reviews.rating will be blank in Liquid. The aggregate rating only exists on an external API, meaning structured data must be generated by JavaScript - with the crawlability problems that entails.FiveOh Reviews on Metaobjects stores ratings in Shopify's standard Metaobjects - so your aggregateRating JSON-LD is rendered in Liquid, readable by Googlebot on first crawl.
Get more information →4. Individual Review markup
Google can display individual review snippets in search results when schema.org/Review objects are nested inside the Product schema. These are separate from the aggregate rating and give Google richer context about your review content.
{%- assign reviews = product.metafields.reviews.product_reviews.value -%}
{%- if reviews != blank -%}
,"review": [
{%- for review in reviews limit: 5 -%}
{
"@type": "Review",
"reviewRating": {
"@type": "Rating",
"ratingValue": "{{ review.fields.rating.value }}",
"bestRating": "5",
"worstRating": "1"
},
"name": {{ review.fields.body.value | truncate: 100 | json }},
"reviewBody": {{ review.fields.body.value | json }},
"author": {
"@type": "Person",
"name": {{ review.fields.author.value | json }}
},
"datePublished": "{{ review.fields.date.value | date: '%Y-%m-%d' }}"
}{% unless forloop.last %},{% endunless %}
{%- endfor -%}
]
{%- endif -%}Include this inside the Product JSON-LD object, after the aggregateRating block. The limit: 5 keeps the JSON-LD block to a reasonable size - Google does not require all reviews to be in the structured data, and a smaller block reduces page weight.
5. BreadcrumbList schema
BreadcrumbList structured data enables the breadcrumb trail to appear in your Google Search listing - replacing or supplementing the URL display. It is especially valuable for product pages reached via a collection.
{%- comment -%}
snippets/structured-data-breadcrumb.liquid
Render this when a collection context is available (e.g. on product pages
linked from a collection, or on collection pages).
{%- endcomment -%}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": {{ shop.name | json }},
"item": {{ shop.url | json }}
}
{%- if collection -%}
,{
"@type": "ListItem",
"position": 2,
"name": {{ collection.title | json }},
"item": {{ shop.url | append: collection.url | json }}
}
,{
"@type": "ListItem",
"position": 3,
"name": {{ product.title | json }},
"item": {{ shop.url | append: product.url | json }}
}
{%- else -%}
,{
"@type": "ListItem",
"position": 2,
"name": {{ product.title | json }},
"item": {{ shop.url | append: product.url | json }}
}
{%- endif -%}
]
}
</script>The collection object is available in Liquid when a product is browsed in the context of a collection (via a /collections/my-collection/products/my-product URL). It will be blank when the product is accessed directly via /products/my-product.
This breadcrumb markup, like all structured data in FiveOh Reviews on Metaobjects, is output server-side - Googlebot picks it up immediately without needing to execute JavaScript.
Get more information →6. Validating with Google's Rich Results Test
After implementing structured data, validate it before expecting results in Search:
- Rich Results Test: Enter your product URL. Shows detected schemas, warnings, and errors. Use this to confirm
aggregateRatingis present and valid. - Schema.org Validator: Paste your raw JSON-LD to check against the schema.org spec directly - useful for catching type mismatches before deploying.
- Google Search Console - Rich results status: After Google re-crawls your pages, the "Rich results" report in Search Console shows which URLs have valid structured data and which have errors.
After deploying, use Search Console's URL Inspection to request re-indexing for key product pages. Stars typically start appearing in search results within 2–6 weeks of Google processing the updated structured data.
7. Common mistakes
- Duplicate
Productschemas on one page. Many themes already include Product JSON-LD. Adding a second block with different data confuses Google. Check your theme'ssnippets/directory for existing structured data before adding new blocks. - Price without currency.
priceCurrencyis required alongsidepricein the Offer. Omitting it causes a validation error. - Rating outside the stated range. If
bestRatingis 5 butratingValueis 4.7, that is valid. IfratingValueexceedsbestRating, Google rejects the schema. - Generating structured data in JavaScript. If the JSON-LD block is injected by a client-side script, Googlebot reads it in its second-pass rendering queue - potentially delayed by days. Always output structured data in Liquid, not JavaScript.
- Using
reviewCount: 0. AnaggregateRatingwith zero reviews is invalid per Google's spec. Always guard withif rating != blankbefore outputting the block.
Written by Marius Korbmacher
Lead Developer at FiveOh Reviews on Metaobjects
Further reading
Related articles
Frequently asked questions
Where does JSON-LD go in a Shopify Liquid theme?
JSON-LD should be placed in the head of your page or at the end of the body. In Shopify Liquid, the cleanest approach is to add it to your theme.liquid layout file or to the specific section or template file for the page type it applies to - for example, product.liquid for Product schema.
Does Shopify automatically add JSON-LD structured data?
Some Shopify themes include basic JSON-LD for Product and BreadcrumbList. However, AggregateRating markup for reviews - which enables star ratings in Google - is rarely included by default and must be added manually or via a compliant review app that outputs it server-side.
What is the difference between JSON-LD and microdata for Shopify structured data?
JSON-LD is a separate script block that describes your page entities in structured JSON. Microdata is markup embedded directly into your HTML using attributes like itemscope and itemprop. Google supports both, but recommends JSON-LD as it is easier to maintain, does not interfere with your HTML structure, and can be cleanly templated in Liquid.
Why is my JSON-LD structured data valid but not showing rich results in Google?
Valid structured data is necessary but not sufficient for rich results. Google also requires the page to meet eligibility guidelines, the structured data to match the visible page content, and - crucially - the markup to be present in the raw HTML at crawl time, not injected by JavaScript after the page loads.
How do I validate my JSON-LD structured data in Shopify?
Use Google's Rich Results Test at search.google.com/test/rich-results with your live product page URL. It shows which structured data types are detected, whether they are valid, and whether they qualify for rich results. For development, you can paste raw HTML directly. Schema.org's validator at validator.schema.org also checks markup against the spec.
Can I have multiple JSON-LD script blocks on one Shopify page?
Yes. Google supports multiple JSON-LD blocks on a single page and will read all of them. A common pattern is a Product block in the main product template, an AggregateRating block added by a review section, and a BreadcrumbList block in the layout file - all independently valid and parsed together.
Which structured data types does Google support for Shopify product pages?
For product pages, Google supports: Product (required), Offer (price, availability), AggregateRating (average star score and review count), Review (individual reviews), and BreadcrumbList (navigation path). Of these, AggregateRating is the one that enables star ratings in search results and has the most visible impact on click-through rates.
Does JSON-LD structured data help with Google AI Overviews and AI search engines?
Yes, and this is becoming increasingly important. AI search engines like Google AI Overviews, Perplexity, and ChatGPT Browse read structured data to understand your pages accurately. JSON-LD in your HTML tells these systems precisely what your product is, its price, its rating, and who reviewed it - in a machine-readable format they can cite with confidence. Server-side JSON-LD is far more reliably processed by AI crawlers than JavaScript-injected markup.
FiveOh Reviews on Metaobjects
Reviews stored in Shopify. Rendered in Liquid. Yours to keep.
The review app that writes to Shopify's standard product review Metaobjects - server-side rendering, no JavaScript widget, no external dependency, no vendor lock-in.
