New: The $100M AI Visibility Roadmap, built from 7,960 live pages and 197 A/B tests. Get it free →

Home / Blog / Product Schema for Shopify: Fields, Liquid and Testing

Technical SEO · 12 min read

Product Schema for Shopify: Fields, Liquid and Testing

Most Shopify themes already output product schema through the structured_data filter. Here's what it covers, what it misses, and how to extend and test it without duplicate markup.

Robert Battle · Updated

Key takeaways
  • Shopify's structured_data filter outputs a Product for single-variant products and a ProductGroup for products with variants, and Dawn includes it in main-product.liquid.
  • Merchant listings require name, image and an Offer with a price above zero and a currency; everything else is recommended.
  • On Shopify's Dawn demo store, the default output had no ratings, shipping, return policy, itemCondition or variesBy.
  • Duplicate Product blocks from the theme and apps, with conflicting prices or stock, are a more common problem than missing markup.
  • Set shipping and return policies in Merchant Center or Search Console, which take precedence over markup, before writing custom code.

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.

Minimum required fields by Google product featureEverything else, including ratings, GTIN and availability, is recommended
1 + 1Product snippetname, plus one of offers, review or aggregateRating
3Merchant listing Productname, image and offers (an Offer, not AggregateOffer)
2Merchant listing Offerprice above zero and priceCurrency

Source: Google Search Central, Product snippet and Merchant listing structured data docs, checked October 2026

PropertyProduct snippetMerchant listingWhere it comes from in Shopify
nameRequiredRequiredProduct title
imageNot requiredRequiredProduct or variant media
offersOne of offers, review or aggregateRatingRequired, and must be an Offer, not AggregateOfferVariant price and inventory
offers.price and priceCurrencyPart of the offerRequired, price above zeroVariant price, store or market currency
availabilityRecommendedRecommendedVariant inventory status
gtin, brand, skuRecommendedRecommendedBarcode, vendor, SKU fields
aggregateRating, reviewRecommendedRecommendedYour 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

Offer properties in Dawn's default output vs Google's listOne product page on Shopify's Dawn demo store, checked October 27, 2026
Required Offer properties (Google)2
Required ones in Dawn output2
Recommended Offer properties (Google)8
Recommended ones in Dawn output2
View the data
Value
Required Offer properties (Google)2
Required ones in Dawn output2
Recommended Offer properties (Google)8
Recommended ones in Dawn output2

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 variesBy on the ProductGroup, and no color or size on each variant. Google lists both as recommended for variants.
  • No aggregateRating or review. Ratings come from your review app, not the filter.
  • No shippingDetails, hasMerchantReturnPolicy or itemCondition on the offers.
  • An empty "category": "" on that store, because no product category was set.
Get a product page SEO plan for your store

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.

Get my product SEO plan →

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.

  1. 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.
  2. 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.
  3. Name the source of each block. In your theme code, search for application/ld+json and structured_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.
  4. 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.
  5. 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.
  6. Check Search Console. The Merchant listings and Product snippets reports under Shopping show which pages Google has parsed and which have errors or warnings.
SymptomLikely causeFix
Two Product blocks with different pricesTheme plus an SEO or feed appKeep one source; disable the app's product output
"Missing field image" or "offers"Custom or old theme with hand-written markupRestore the structured_data filter or fix the template
Stars in the test but never in searchRating not visible on the page, or not eligibleShow the aggregate rating on the page; markup alone isn't enough
Rating block appears only after renderingReview app injects it with JavaScriptConfirm Google renders it in the Rich Results Test
Sold-out products show "InStock"Hardcoded availability in a custom snippetRead variant.available instead
Price warning on markets or currenciesPrice and currency come from different contextsOutput 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 json filter escapes quotes and line breaks, which is what breaks most hand-written schema.
  • Shopify stores prices in cents, so divided_by: 100.0 converts to a decimal. cart.currency.iso_code is the currency the shopper is browsing in, which keeps price and currency from the same context on multi-currency stores.
  • image_url returns a protocol-relative URL, so the snippet prepends https:.
  • It describes only the selected or first available variant. For products with real variants, follow Google's ProductGroup pattern (a hasVariant list, 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.

Make your products the ones AI answers cite

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.

Get the free roadmap →

Variants, markets and other Shopify edge cases

SituationWhat to doWhy
Product with size or color variantsUse ProductGroup with hasVariant, and add variesBy plus size or color per variantGoogle'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= URLMatches Google's single-page variant pattern
Same product in several collectionsMarkup lives only on the product page, not on collection gridsCollection pages aren't pages that sell one specific product
Multiple currencies with Shopify MarketsOutput price and currency from the same rendered contextA price in one currency labeled as another is a mismatch
Sold-out or discontinued itemsKeep the page and mark OutOfStock, or redirect if it's gone for goodAvailability must match the page
Bundles and gift cardsDescribe what's actually sold, with a real priceMerchant 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

  1. 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.
  2. Flip real states. Put a test product on sale, then sell it out, and confirm the markup follows each change without a code edit.
  3. 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.
  4. 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.
  5. 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.

Want us to review your product templates?

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.

Get my custom plan →
FAQ

Questions & answers

Does Shopify add product schema automatically?
Most current themes do. Dawn and many themes built on it output product JSON-LD with the structured_data Liquid filter in the main product section. Older or custom themes may not, so check your own product pages.
Do I need an app for Shopify product schema?
Usually not for the product and offer, because the theme handles them. Apps are useful for review ratings, but only enable product markup in one app so you don't publish duplicate Product blocks.
Why don't my star ratings show in Google?
Valid markup makes a page eligible but doesn't guarantee a rich result. Check that the rating is visible on the page, that the markup includes ratingValue and a rating or review count, and that only one block describes the product.
Should I use Product or ProductGroup for variants?
Use ProductGroup with each variant as a nested Product in hasVariant, plus productGroupID and variesBy. Shopify's structured_data filter already outputs a ProductGroup for products with variants.
Will product schema improve my rankings?
Google says structured data doesn't guarantee a rich result, and it isn't a ranking shortcut. Its value is eligibility for richer product results, which can make your listing more useful to shoppers.
Free custom plan

Find the revenue you're leaving on the table

A free 30-minute call where we build the first version of your plan with you. You leave with a plan either way.

  • Your score across Google, AI citations, branded demand, and reviews
  • The pages on your site that are ready to move right now
  • Your first Flagship page and the first three tests we'd run

Step 1 of 3

Get your free custom plan

Free. Takes about 30 seconds.