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

Fixing Merchant Listing Errors in Shopify Product Schema

Most of the yellow in Search Console's Merchant listings report is warnings you can ignore. Here are the Shopify product schema errors that actually block rich results, and the Liquid edits that clear them.

Fixing Merchant Listing Errors in Shopify Product Schema

If you landed here from Search Console with a wall of yellow in the Merchant listings report, start by reading the column headings. Almost everything in there is a warning, not an error, and warnings don't remove you from rich results. The two that show up on nearly every Shopify store are missing shippingDetails and missing hasMerchantReturnPolicy, and neither one needs a Liquid edit if you have a Google Merchant Center account. The Shopify product schema errors worth your Friday afternoon are the invalid ones: a price that parsed as 2 because of a comma, an empty image array, a second Product block from a reviews app, and product templates that emit no schema at all.

Here's the order we work through it, and the specific edits that clear each item.

Merchant listings and Product snippets are two different reports

Search Console splits product structured data into two enhancement reports. Product snippets covers the older rich result: rating stars, price, stock. Merchant listings is the shopping-oriented experience, and it wants more from you: price, currency, availability, plus shipping and returns to be eligible for the fuller treatment in the Shopping tab and the product grids.

A page can be valid in one report and broken in the other. That confuses people. If a URL appears with an error under Merchant listings but is clean under Product snippets, you're missing something specific to the merchant experience, usually a currency or an offer field, not the whole schema block.

Errors block eligibility. Warnings reduce how rich the result can be. Sort the report by error count first and ignore the warnings entirely until the errors are at zero.

Find out how many Product nodes your page actually emits

Before you touch main-product.liquid, view source on a live product page and count the application/ld+json blocks. On a store that's been running three years with a reviews app, an SEO app and a theme that ships its own markup, three or four is normal. Two of them will be Product.

Google doesn't merge them cleanly. It picks, and it often picks the thin one. So you can spend an hour adding hasMerchantReturnPolicy to the theme's block and see no change in the report, because the block being indexed came from an app you forgot you installed.

Deduplicate first:

  • Reviews apps almost always have a toggle for their own structured data. Turn it off and let the theme own the Product node, then feed aggregateRating into the theme block from the app's metafields.
  • SEO apps that "add missing schema" are the usual culprit for duplicates. If it's writing Product markup and your theme is too, one of them has to go.
  • Check alternate product templates. If you built product.preorder.json or product.bundle.json from a blank template, it probably has no schema at all. We miss at least one of these on the first pass most of the time, because the failing URLs sit at the bottom of a 900-row export.

The Shopify product schema errors that actually cost you the rich result

These are the ones marked as errors, and they all have the same root cause: the Liquid produced output that isn't valid JSON, or produced nothing.

Invalid price format. The single most common one we find. Someone edited the schema to use money_without_currency so the number would "look right". On an INR store that filter outputs 2,499.50. A comma inside a JSON number breaks the parse. Google either drops the offer or reads the value as 2. Use the raw cents divided by 100, with a decimal in the divisor.

Truncated price. Dividing by 100 instead of 100.0 does integer division in Liquid. A variant priced at 249950 cents becomes 2499, not 2499.5. The page shows ₹2,499.50 and the markup says 2499, and you get a mismatched value warning that no amount of re-crawling will fix. One character.

Missing image. If the schema pulls from the selected variant's image and the variant has none, you get an empty array. Fall back to product.featured_image, then to a placeholder you control. Merchant listings needs an image.

Currency mismatch under Markets. Older themes and a lot of custom builds hardcode shop.currency. Once you enable a second market with price conversion, the page renders in AED or USD while the markup insists on INR. Use the currency from the cart or the active localization object so it tracks whatever the visitor, including Googlebot, is being served.

Blank name or description. Happens when a theme reads title or description from a metafield that isn't populated on every product. A missing description is a warning. A missing name is an error.

Worked example: the 50-paise mismatch

Take a product at ₹2,499.50. Shopify stores that as 249950. In Liquid, 249950 | divided_by: 100 returns 2499 because both operands are integers. 249950 | divided_by: 100.0 returns 2499.5.

Google reads 2499 from your markup and ₹2,499.50 from the visible page. Difference: 0.50. Small enough that nobody notices in the theme, large enough that Search Console flags a mismatched value and the offer gets treated as unreliable. If your catalogue prices all end in round hundreds you'll never see this bug. Add one product at ₹1,299.50 and it appears.

Same class of problem with GST. If you sell B2B with prices shown exclusive of tax and your schema emits the tax-exclusive figure while the page displays an inclusive number in a nearby element, expect the mismatch flag. Decide which number is the offer price and make both places agree.

Clearing shippingDetails and hasMerchantReturnPolicy

The unwelcome answer first: for most Indian and GCC stores, do not hand-code these in Liquid. Set them once in Google Merchant Center, under shipping services and return policies, and Google will apply them to your listings. It takes twenty minutes, it covers every product, and you don't inherit a maintenance job every time you change a courier rate.

Markup is the right call in two situations: you don't have a Merchant Center account and don't want one, or your shipping cost genuinely varies by product in a way an account-level policy can't express (heavy furniture, cold-chain food, jewellery with insured shipping).

If you're doing it in Liquid, the return policy object needs applicableCountry, returnPolicyCategory, and where the category is a finite window, merchantReturnDays, plus returnMethod and returnFees. A 7-day window with free return by post is four lines. Hardcode it in a snippet rather than a metafield unless your policy actually differs per product.

Shipping details needs a rate as a monetary amount with a value and currency, a destination with a country code, and a delivery time split into handling and transit ranges in days. Free shipping over ₹999 with 3 to 6 day transit is a small object. Free shipping to 19,000 pin codes with three courier tiers is not, and that's the version you'll regret.

Variants: one Offer each, or an AggregateOffer

Shopify themes typically loop every variant and write an Offer for each, with the variant's SKU, barcode mapped to gtin12 or gtin13 by length, availability and price. That's fine and Google accepts it.

Two things go wrong. First, each Offer's URL includes ?variant=, and the canonical is the bare product URL, so Google sometimes reports the offers as pointing off-page. Keep the variant URLs; they're the honest answer, and the canonical handles the rest. Second, on a 40-variant apparel product the offers array gets long enough to bloat the HTML for no benefit. Switch to an AggregateOffer with lowPrice, highPrice, priceCurrency and offerCount once you're past roughly a dozen variants.

If you sell the same garment in colour and size combinations and want each shown as a distinct product, ProductGroup with hasVariant and variesBy is the correct structure. It's more work than it sounds and only pays back on large apparel catalogues.

aggregateRating: the one that gets stores penalised

Missing review and missing aggregateRating are warnings. Leave them missing if you have no reviews. Do not, under any circumstances, let an app write a 4.8 rating for a product with zero reviews, and don't emit a rating that isn't visible to a human on the same page. That's a review snippet spam manual action, and losing every rich result on the domain costs far more than the stars were worth.

If the ratings are real, pull them from the review app's product metafields, guard the block with a check that the count is above zero, and make sure the number rendered in the schema matches the number rendered in the badge.

Availability, preorders and the out-of-stock lag

Availability must be one of Google's enum values. InStock and OutOfStock cover most cases. Two Shopify-specific traps:

  • "Continue selling when out of stock" makes variant.available true with zero inventory. Your schema says InStock. That's technically consistent with the page, since the buy button works, but if the product is really a preorder, use PreOrder or BackOrder and add the expected date.
  • Sold-out variants inside a live product still get an Offer written. That's correct. Don't filter them out; Google wants to know the variant exists and can't be bought.

A validation loop that doesn't eat a week

Search Console's report lags. Fix, then verify locally before you wait on Google:

  1. Run the URL through the Rich Results Test and confirm it detects one Product item, not two, and that it's eligible for Merchant listings.
  2. Use URL Inspection, Test live URL, and read the rendered structured data. This catches app-injected schema that only appears client-side.
  3. Pick one failing URL per error type, not per URL. Fifty errors usually trace to three causes.
  4. Then hit Validate fix in the report and leave it alone. Reprocessing across a full catalogue takes a couple of weeks and clicking it again doesn't speed it up.

Where this sits in the priority list

Clean product markup makes your existing rankings look better in the SERP. It doesn't create rankings. If your product pages take 4 seconds to render their largest element on a mid-range Android over 4G, the schema is not your problem, and we'd rather spend the same two days on store speed and let the schema stay yellow for a month.

Where it does earn its keep: competitive categories where every result in the grid shows a price and yours doesn't, and any store running Shopping campaigns off the same feed, because the markup and the feed disagreeing is a slow leak nobody watches. Fixing both together is usually a half-day job on a standard theme and a two-day job on a heavily customised one. Our SEO and content team handles it alongside the on-page work rather than as a standalone ticket, because the same person needs to see the theme code and the Merchant Center feed.

Do this today: open one product page, view source, count the ld+json blocks. If the answer is more than one Product, you found your bug before you read any documentation. If you'd rather someone else read the 900-row export, our free store audit covers structured data, and you'll get the specific Liquid lines back, not a PDF of screenshots.

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