Product schema on Shopify is JSON-LD that tells Google a page sells a specific product, at a specific price, in a specific stock state. Most modern Shopify themes, including Dawn, already output it through Liquid's structured_data filter, so the job is usually to audit and extend what your theme publishes, not to paste in a second script.
This guide is for Shopify merchants, marketers and developers who want product pages eligible for Google's product rich results and merchant listings. You'll get the exact fields Google requires, what Dawn outputs out of the box (we checked a live store), the gaps worth filling, copy-ready JSON-LD and Liquid, and a step-by-step test before you ship anything.
What product schema on Shopify actually does
Product structured data is a block of JSON-LD in the page's HTML that describes the item using the schema.org vocabulary: its name, image, brand, identifiers and an Offer with price, currency and availability. Google uses it for two different search features, and they have different rules.
- Product snippets are the extra lines under a normal result: star ratings, price, availability. Any page about a product can qualify, including editorial review pages.
- Merchant listings are the richer shopping experiences in Search, Images and the Shopping tab, and they are only for pages where a shopper can buy the product from you.
Two rules from Google's general structured data guidelines shape everything else. First, "Don't mark up content that is not visible to readers of the page." Second, "Google does not guarantee that your structured data will show up in search results." Markup makes a page eligible; it doesn't force a rich result or move rankings on its own. For the wider picture of how schema fits AI search, see our schema markup guide.
Required fields for product snippets and listings
Google publishes separate checklists for product snippets and merchant listings. A Shopify product page is a merchant page, so build to the stricter merchant listing list and you cover both.
Source: Google Search Central, Product snippet and Merchant listing structured data docs, checked October 2026
| Property | Product snippet | Merchant listing | Where it comes from in Shopify |
|---|---|---|---|
name | Required | Required | Product title |
image | Not required | Required | Product or variant media |
offers | One of offers, review or aggregateRating | Required, and must be an Offer, not AggregateOffer | Variant price and inventory |
offers.price and priceCurrency | Part of the offer | Required, price above zero | Variant price, store or market currency |
availability | Recommended | Recommended | Variant inventory status |
gtin, brand, sku | Recommended | Recommended | Barcode, vendor, SKU fields |
aggregateRating, review | Recommended | Recommended | Your review app, if it outputs markup |
Recommended fields that matter most
On merchant listings, Google's recommended Offer properties are availability, itemCondition, priceValidUntil, shippingDetails, hasMerchantReturnPolicy, url, validFrom and validThrough. On the Product, the useful ones for most stores are a GTIN (gtin, gtin8, gtin12, gtin13, gtin14 or isbn), brand.name, description, sku and mpn, plus color, size and material for variants.
Two details trip stores up. The product snippet docs note a snippet "may not display if the priceValidUntil property indicates a past date," so never hardcode a date that will expire. And the pros and cons appearance is only for editorial review pages, not merchant product pages, so don't add positiveNotes to your own product pages expecting a result.
What Shopify's structured_data filter outputs
Shopify's Liquid reference documents the structured_data filter, which converts a product or article object into schema.org JSON-LD. Products with variants are output as a ProductGroup; products without variants are output as a Product. Dawn calls it in sections/main-product.liquid:
<script type="application/ld+json">
{{ product | structured_data }}
</script>
We fetched a product page on Shopify's live Dawn demo store (theme-dawn-demo.myshopify.com) on October 27, 2026 to see the real output. A six-variant product returned a ProductGroup like this (trimmed to one variant):
{
"@context": "http://schema.org/",
"@id": "/products/puff-olive-leaf#product",
"@type": "ProductGroup",
"name": "Puff",
"brand": { "@type": "Brand", "name": "Mlouye" },
"description": "A streamlined approach to daily essentials...",
"productGroupID": "6920737783897",
"url": "https://theme-dawn-demo.myshopify.com/products/puff-olive-leaf",
"hasVariant": [
{
"@type": "Product",
"name": "Puff - Olive Leaf",
"sku": "10-039-065",
"gtin": "8682035409243",
"image": "https://theme-dawn-demo.myshopify.com/cdn/shop/products/...",
"offers": {
"@type": "Offer",
"price": "465.00",
"priceCurrency": "CAD",
"availability": "http://schema.org/OutOfStock",
"url": "https://theme-dawn-demo.myshopify.com/products/puff-olive-leaf?variant=39577056936025"
}
}
]
}
A single-variant product on the same store returned a plain Product with one Offer. The same page also carried an Organization block from Dawn's header, with the store name, logo and social profiles.
What the default output covers, and what it leaves out
View the data
| Value | |
|---|---|
| Required Offer properties (Google) | 2 |
| Required ones in Dawn output | 2 |
| Recommended Offer properties (Google) | 8 |
| Recommended ones in Dawn output | 2 |
Source: Google Search Central, Merchant listing structured data (2026); Shopify Dawn demo store product page, October 27, 2026
The good news: every required merchant listing field is there, variants are grouped with productGroupID and hasVariant, each variant has its own ?variant= URL, and GTIN appears when the variant has a barcode. That matches the single-page pattern in Google's product variant docs.
The gaps we saw on that page:
- No
variesByon theProductGroup, and nocolororsizeon each variant. Google lists both as recommended for variants. - No
aggregateRatingorreview. Ratings come from your review app, not the filter. - No
shippingDetails,hasMerchantReturnPolicyoritemConditionon the offers. - An empty
"category": ""on that store, because no product category was set.
We'll benchmark your product pages against your top three competitors on Google and AI answers and list the prioritized fixes, schema included, with the revenue math.
Audit your store before you add anything
The most common product schema problem on Shopify isn't missing markup. It's duplicate markup: the theme outputs one Product, a review app outputs another, and an SEO app outputs a third, sometimes with a different price or stock status. Google says it understands multiple items on a page, but two descriptions of the same product with different facts is a data quality problem you should fix.
- Pick test pages. Choose one simple product, one with several variants, one on sale with a compare-at price and one that is sold out.
- Pull every JSON-LD block. Paste each URL into our Schema Markup Validator, which lists every JSON-LD block on a page and checks the properties Google needs. Then run Google's Rich Results Test, which shows what Google itself detects after rendering.
- Name the source of each block. In your theme code, search for
application/ld+jsonandstructured_data. In the browser, compare the raw HTML (View source) with the rendered DOM. A block that only appears after rendering was injected by JavaScript, usually an app. - Compare the facts. Check name, price, currency, availability and rating count in each block against what a shopper sees on the page. Any mismatch is the first thing to fix.
- Decide one owner per fact. Usually the theme owns the product and offer, and the review app owns the rating. Turn off duplicate product output in apps that let you, or remove the theme block if you're replacing it fully.
- Check Search Console. The Merchant listings and Product snippets reports under Shopping show which pages Google has parsed and which have errors or warnings.
| Symptom | Likely cause | Fix |
|---|---|---|
Two Product blocks with different prices | Theme plus an SEO or feed app | Keep one source; disable the app's product output |
| "Missing field image" or "offers" | Custom or old theme with hand-written markup | Restore the structured_data filter or fix the template |
| Stars in the test but never in search | Rating not visible on the page, or not eligible | Show the aggregate rating on the page; markup alone isn't enough |
| Rating block appears only after rendering | Review app injects it with JavaScript | Confirm Google renders it in the Rich Results Test |
| Sold-out products show "InStock" | Hardcoded availability in a custom snippet | Read variant.available instead |
| Price warning on markets or currencies | Price and currency come from different contexts | Output both from the same variant and currency |
How to extend Shopify product schema safely
For most stores, the right move is to keep the structured_data filter for the product and offer and fill the gaps around it. That way Shopify keeps prices, variants and availability in sync, and you only own the extras.
Add returns and shipping once, sitewide
Google's merchant listing docs recommend putting a standard shipping and return policy under your Organization markup, and using offer-level shippingDetails and hasMerchantReturnPolicy only to override it. The return policy docs require either applicableCountry plus returnPolicyCategory (and merchantReturnDays for a finite window), or a merchantReturnLink. A minimal version looks like this:
{
"@context": "https://schema.org",
"@type": "OnlineStore",
"name": "Example Store",
"url": "https://www.example.com",
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"applicableCountry": "US",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": 30,
"returnMethod": "https://schema.org/ReturnByMail",
"returnFees": "https://schema.org/FreeReturn"
}
}
Use your real policy values. There's also a simpler route: Google says return and shipping policies set in Merchant Center or Search Console take precedence over markup, and for returns, if you provide both markup and Search Console settings, Google will only use Search Console. Many Shopify stores already sync with Merchant Center through the Google and YouTube app, so check there before you write any markup.
Show a sale price correctly
When a product is on sale, the active price is the sale price. Google's docs say to show the original price with a priceSpecification whose priceType is https://schema.org/StrikethroughPrice, and not to set priceType on the active price:
"offers": {
"@type": "Offer",
"price": 79.00,
"priceCurrency": "USD",
"priceSpecification": {
"@type": "UnitPriceSpecification",
"priceType": "https://schema.org/StrikethroughPrice",
"price": 99.00,
"priceCurrency": "USD"
}
}
In Liquid, the original price is the variant's compare_at_price. Only output the strikethrough block when compare_at_price is greater than price, and make sure the page visibly shows both prices.
Let your review app own ratings
Google's review snippet rules require ratingValue and at least one of ratingCount or reviewCount, and say users should be able to see the aggregate rating on the page. Most review apps can output this for you. Turn it on in one app only, then confirm it attaches to the same product (ideally nested in the product, or linked with the same @id) rather than creating a second, partial Product.
Writing your own Product JSON-LD in Liquid
Some stores replace the filter entirely, for example on a headless or heavily customized theme. If you do, generate every value from Liquid objects so the markup can't drift from the page. Here is a compact, single-offer starting point for a product template:
{%- assign v = product.selected_or_first_available_variant -%}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"@id": "{{ shop.url }}{{ product.url }}#product",
"name": {{ product.title | json }},
"description": {{ product.description | strip_html | strip | json }},
"image": {{ product.featured_image | image_url: width: 1200 | prepend: 'https:' | json }},
"url": "{{ shop.url }}{{ product.url }}",
"sku": {{ v.sku | json }},
{%- if v.barcode != blank %}
"gtin": {{ v.barcode | json }},
{%- endif %}
"brand": { "@type": "Brand", "name": {{ product.vendor | json }} },
"offers": {
"@type": "Offer",
"url": "{{ shop.url }}{{ v.url }}",
"price": {{ v.price | divided_by: 100.0 | json }},
"priceCurrency": {{ cart.currency.iso_code | json }},
"availability": "https://schema.org/{% if v.available %}InStock{% else %}OutOfStock{% endif %}",
"itemCondition": "https://schema.org/NewCondition"
}
}
</script>
Notes on this snippet:
- The
jsonfilter escapes quotes and line breaks, which is what breaks most hand-written schema. - Shopify stores prices in cents, so
divided_by: 100.0converts to a decimal.cart.currency.iso_codeis the currency the shopper is browsing in, which keeps price and currency from the same context on multi-currency stores. image_urlreturns a protocol-relative URL, so the snippet prependshttps:.- It describes only the selected or first available variant. For products with real variants, follow Google's
ProductGrouppattern (ahasVariantlist,productGroupID,variesBy) or keep the filter, which already does this. - Remove the filter's block when you add this one, or you'll publish two products.
If you'd rather not touch Liquid, our Schema Markup Generator builds Product JSON-LD you can adapt, and you can check the result in the validator before it goes live.
Clean product data is the start. The free $100M AI Visibility Roadmap, built from 7,960 live pages and 197 A/B tests, covers getting cited by ChatGPT, Google AI Overviews and Perplexity.
Variants, markets and other Shopify edge cases
| Situation | What to do | Why |
|---|---|---|
| Product with size or color variants | Use ProductGroup with hasVariant, and add variesBy plus size or color per variant | Google's variant docs list these as recommended; the default output we saw omits them |
Variant URLs with ?variant= | Keep the product URL as canonical and give each variant offer its ?variant= URL | Matches Google's single-page variant pattern |
| Same product in several collections | Markup lives only on the product page, not on collection grids | Collection pages aren't pages that sell one specific product |
| Multiple currencies with Shopify Markets | Output price and currency from the same rendered context | A price in one currency labeled as another is a mismatch |
| Sold-out or discontinued items | Keep the page and mark OutOfStock, or redirect if it's gone for good | Availability must match the page |
| Bundles and gift cards | Describe what's actually sold, with a real price | Merchant listings need a price above zero |
Variant handling is also where on-page product SEO and schema meet. Our Shopify product SEO guide covers titles, descriptions and variant URLs, and our e-commerce schema guide covers the other types worth adding around product pages, such as breadcrumbs and organization markup.
Test and monitor product schema
- Before release, test a duplicate theme. Preview your edited theme and run the four test pages through the validator and the Rich Results Test. Look for one product entity per page with the right price and stock.
- Flip real states. Put a test product on sale, then sell it out, and confirm the markup follows each change without a code edit.
- Publish, then request indexing on a handful of important product URLs in Search Console's URL Inspection tool, and check the detected items in the live test.
- Watch the Shopping reports. In the weeks after launch, review the Merchant listings and Product snippets reports for new errors and the count of valid items.
- Re-test after every theme or app update. Theme updates and new apps are when duplicate or broken markup usually appears.
Errors block eligibility for that feature; warnings mean a recommended field is missing and the result may be less rich. Fix errors first, then work through warnings in order of how much they help shoppers: availability, ratings, shipping and returns.
A practical product schema plan for your store
If you do nothing else this week: audit four product pages, remove any duplicate product markup, and set your shipping and return policies in Merchant Center or Search Console. Then add variant attributes and ratings where your theme and review app support them, and re-test after every theme update. That order fixes the issues most likely to cost you eligibility first and leaves the nice-to-haves for later.
If you want a second set of eyes on your product templates, or help prioritizing structured data alongside the rest of your technical SEO, our technical SEO team can help, or you can get in touch for a custom plan.
Book a free 30-minute call. We'll compare your store with your top three competitors, show which pages are ready to move and prioritize the fixes by revenue.