Free Shopify store auditSpeed, SEO and conversion leaks — no cost, no obligation.
Claim it
Thriftizer Solutions LLPShopify Select Partner
Book a Growth Audit
SEO Sep 6, 2026 9 min read

Product Schema on Shopify: Getting Rich Results That Actually Show

Your theme already emits Product JSON-LD, an app probably duplicated it, and Google is ignoring both. A practical guide to diagnosing Shopify structured data that validates but never shows.

Product Schema on Shopify: Getting Rich Results That Actually Show

If you came here typing shopify structured data, the short answer is that your theme almost certainly already outputs Product JSON-LD, an app has probably added a second copy of it, and Google is either ignoring both or picking the wrong one. Valid markup and a rich result are two different things. The Rich Results Test can return a clean green pass on a page that will never show a price in search, and nothing in Shopify's admin will tell you why.

So this is a diagnosis post. What the theme emits, what duplicates it, which fields Google now actually needs for a merchant listing, and how to tell "broken" apart from "eligible but not chosen".

What your theme already emits

Every Online Store 2.0 theme we work in ships some product structured data. Dawn and the themes built on it emit a Product node from the main product section; recent versions lean on Shopify's structured_data Liquid filter, older ones hand-build the JSON in a snippet. Most paid themes do the same, plus an Organization node and a WebSite node with a SearchAction in theme.liquid.

Before you add anything, look. View source on a product page, search for application/ld+json, and count the blocks. Then paste the page into Google's Rich Results Test and read the detected items list rather than the pass/fail badge. Two Product entries is the single most common thing we find on a first audit, and it is not a cosmetic problem.

How to add structured data to Shopify when the theme falls short

Three routes, in order of how often they are the right one.

Edit the theme. Find the existing JSON-LD snippet, extend it, done. If your theme hand-builds the Product node, you can add brand, gtin13, hasMerchantReturnPolicy and shippingDetails in the same block. Keep it server-rendered in Liquid. Google renders JavaScript, but JS-injected schema depends on the rendering queue and we have seen it picked up days later than the HTML around it.

Use metafields for the fields Shopify has no native home for. GTIN lives on the variant barcode field, which is the one identifier Shopify gives you for free. Return window, material, colour, size, energy rating: those go in metafields, and you reference them in the JSON-LD with a fallback so a missing value drops the property instead of printing null.

Use an app. Fine, with caveats, covered below.

One rule regardless of route: the markup describes what the page displays. If the page shows ₹2,499 and the JSON says 2499.00, good. If the page shows a member price and the JSON shows the compare-at price, you have a mismatch that Google's automated checks catch and that manual review treats as spam.

The duplicate Product node, and how to fix it

Here is the mechanism. Your theme emits a Product node. Your review app injects its own Product node so it can attach an aggregateRating. Your all-in-one SEO app injects a third, because it assumes the theme has none. Now the page carries three Product objects with the same URL, possibly three different prices if one of them reads the compare-at price or a currency-converted value.

Google does not merge them helpfully. It picks one, and it tends to pick the first one in the DOM, which is usually whichever app injected earliest. If that one lacks an offer, you lose the price in search even though a perfectly good offer exists forty lines down the page.

The fix, in order: turn off schema output in every app that offers a toggle, keep exactly one source, and if the review app can only attach ratings by injecting its own node, disable the theme's Product node instead and let the app own it. Verify by counting blocks in the rendered source, not the Liquid. Two apps can each be configured "correctly" and still collide.

We get this wrong on the first pass maybe a third of the time, usually because an app injects only on certain templates or only when a product has at least one review. Check a product with reviews and one without.

The ₹2,49,900 bug

Shopify stores money in the smallest unit. product.price for a ₹2,499.00 item returns 249900. If someone writes "price": {{ product.price }} into the JSON-LD, Google reads ₹2,49,900 against a page showing ₹2,499, flags the mismatch, and drops the offer.

249900 divided by 100 is 2499.0. That is the whole fix: {{ product.price | divided_by: 100.0 }}, or the money_without_currency filter with the comma stripped, because Indian number formatting will hand you "2,499.00" and a comma inside a JSON number is a parse error that kills the entire block, not just the price.

Related, and specific to multi-market stores: if you sell into the UAE or the US through Shopify Markets with currency conversion, the visible price changes per visitor and the hardcoded priceCurrency often does not. Use cart.currency.iso_code or the market's currency, and accept that Googlebot crawling from the US will see the US price. That is correct behaviour, not a bug.

For Indian stores quoting GST-inclusive prices, wrap the offer in a priceSpecification with valueAddedTaxIncluded set to true. It costs four lines and removes any ambiguity about whether the number in search is what the customer pays.

Which fields Google now treats as required

The distinction that trips people up: product snippets and merchant listings are not the same feature. A product snippet can appear with just a name, an image and either an offer or a rating. Merchant listing experiences, the ones with price, availability, shipping and returns shown in the shopping surfaces, want more.

Practically, ship all of this on every product page:

  • name, image, description, url with the canonical URL, not the collection-scoped one
  • offers with price, priceCurrency, availability as a full schema.org URL, and priceValidUntil if you run scheduled sales
  • brand as an object with a name, even if the brand is the store
  • a unique identifier: gtin13 from the variant barcode where you have it, mpn or sku otherwise. Private-label and handmade sellers with no GTIN should send sku and set isFamilyFriendly-style guff aside; a missing GTIN limits merchant listing eligibility but does not block product snippets
  • hasMerchantReturnPolicy with the return window in days, who pays for return shipping, and the countries it applies to
  • shippingDetails with a rate and a delivery window split into handling time and transit time

Returns and shipping are the two most commonly missing, and they are also the two that produce visible annotations. If you already run the Google & YouTube channel, Merchant Center can carry shipping and return settings account-wide, which is usually less work than maintaining them in Liquid. Do one or the other. Conflicting values across the feed and the page is a real source of disapprovals.

Variants deserve a note. Shopify themes typically emit one Product with an offers array or an AggregateOffer. Google's preferred structure for a size-and-colour catalogue is a ProductGroup with hasVariant, variesBy and a productGroupID. It is more work and it matters most when your variants have distinct prices or their own URLs. If every variant costs the same, leave it.

BreadcrumbList is worth doing properly. It changes the URL line in the SERP to a readable path and it helps Google understand category structure on stores where products are reachable through several collections. The catch on Shopify is that a product URL has no inherent collection path. If your theme builds breadcrumbs from collection.title when a visitor arrives via a collection and falls back to "Home / Product" otherwise, your markup will be inconsistent across crawls. Pick one canonical path per product, usually from a product type or a primary-collection metafield, and emit that every time.

FAQPage is the one to stop building for search. In August 2023 Google narrowed FAQ rich results to authoritative government and health sites, and dropped HowTo results entirely. Your product FAQ accordion will not produce those expandable questions any more, whatever the app promises. Keep the FAQ content because shoppers read it and it answers real purchase objections. Just do not expect the markup to buy you anything in the results page.

Is there a best schema app for Shopify?

Honest answer: if you are on a maintained OS 2.0 theme and comfortable with a developer touching one snippet, you do not need one. The theme gives you 80% and a couple of hours of Liquid gives you the rest, with no monthly fee and no third-party script to audit later.

An app is the right call in two situations. First, when nobody on the team will ever open the code editor and you need returns and shipping data on 4,000 products without touching the theme. Second, when you need schema types the theme has no concept of, like Article markup across a large blog or LocalBusiness for physical stores.

Whatever you install, the evaluation criterion is not the feature list. It is whether the app can detect and suppress existing theme markup. An app that blindly appends a Product node to a page that already has one has made your search presence worse while reporting success in its dashboard.

Validating properly, and reading the result

Use both tools, for different jobs.

The Rich Results Test tells you whether a page is eligible right now. Run the live URL, not pasted code, so you catch app-injected markup. Read the detected items count first. Warnings about missing recommended fields are worth fixing; they are often the difference between a plain blue link and a merchant listing.

Search Console's enhancement reports tell you what Google has actually indexed across the catalogue, which is the number that matters. A store can pass the test on the one URL you checked and have 900 products with invalid markup because they are out of stock and the theme writes an empty availability string. The Merchant listings and Product snippets reports in Search Console will surface that; a single-URL test never will.

Also use URL Inspection's live test and look at the rendered HTML. That is the only way to see what Googlebot sees after apps have run.

Rich snippets still not showing

You fixed the duplicates, added returns and shipping, everything validates, and search still shows a plain link. Run through this before assuming the markup is at fault.

  • It has been under two weeks. Recrawl and reprocessing take time. Request indexing on a few URLs and wait.
  • Eligible is not guaranteed. Google decides per query whether to show the enhanced treatment. The same URL can show a price on desktop and not on mobile, or show it for a branded query and not a generic one.
  • Price mismatch. Check a discounted product, a sold-out product and a product with a price-hiding B2B rule. These are where the mismatches hide.
  • Self-serving reviews. Marking up ratings you do not display on the page, or ratings collected by the merchant about the merchant rather than the product, gets the whole product snippet suppressed.
  • Site-level quality. New domains and thin catalogues frequently get valid markup that Google simply chooses not to surface. Nothing to fix in the code.
  • A manual action. Rare, but check the Manual Actions panel before spending another week in Liquid.

One more, and it is the sneaky one: if your product pages are slow enough that Googlebot times out on render, JS-injected schema never gets seen. We have traced "missing" markup to a 5s render blocked by three review widgets more than once. Fixing page speed was the fix.

Where to start this week

Open one product page, count the application/ld+json blocks in the rendered source, and open the Merchant listings report in Search Console. Those two checks take ten minutes and will tell you whether you have a duplication problem, a missing-fields problem, or no problem at all. If the answer is duplication, disable app schema first and re-test before writing a line of Liquid.

If you would rather someone else did the counting, our free store audit covers structured data alongside the rest of the technical setup, and we can point a Shopify developer at the theme snippet once you know what is actually wrong. Diagnosis first. It is a cheaper hour than a rewrite.

Previous postNext post

Ready to scale your D2C brand profitably?

Let's build a growth engine that drives more traffic, more conversions and more profit.

Book a Growth Audit
📅 Free Audit💬 WhatsApp