TL;DR
- Shopify's built-in Liquid
structured_datafilter never emitsshippingDetailsorhasMerchantReturnPolicyinside theOfferobject, on any theme, out of the box. It's a filter limitation, not a misconfiguration on a specific client site. - Both fields are "recommended," not "required." Search Console still lists the product as valid; the only thing missing is the shipping/returns annotation on the listing itself.
- Google reads shipping/returns data from four sources in strict precedence (Content API for Shopping, then Merchant Center/Search Console settings, then product-level markup, then Organization markup). Pasting a snippet into a client's theme often changes nothing if a higher-priority source is already set, and only a Shopify store's "General" shipping profile syncs correctly to Merchant Center.
If you build or maintain Shopify stores for clients, you've probably run into this Search Console warning: shippingDetails and hasMerchantReturnPolicy missing from Merchant listings, across an entire catalog, on a store you just launched. It's not a content problem on the client's end. Shopify's built-in Liquid structured_data filter never emits these fields in the Offer object, on any theme, by default. The usual fix, pasting a shipping/returns block into the product template, is a real fix for a real symptom, but it often targets a layer Google isn't even reading. This piece covers why the fields are absent, why they don't actually invalidate a product, the four-source precedence order Google uses for shipping and returns data, a Shopify-specific shipping profile trap that silently desyncs rates from Merchant Center, and a five-minute audit you can run on any client account.
Why doesn't Shopify's offer markup include shipping and returns fields?
Shopify generates product JSON-LD through the Liquid structured_data filter, and its documented output is short. Inside offers it emits exactly six properties:
"offers": {
"@id": "/products/crocodile-tears?variant=39888242344001#offer",
"@type": "Offer",
"availability": "http://schema.org/OutOfStock",
"price": "56.00",
"priceCurrency": "CAD",
"url": "https://…/products/crocodile-tears?variant=39888242344001"
}
No shippingDetails, no hasMerchantReturnPolicy, no priceValidUntil. This isn't something you find and undo in the theme code: it's absent by construction on any theme using the built-in filter, which offers no option to add it. That's why the warning covers a client's whole catalog on day one, on a store nobody has customized yet.
Does this actually invalidate the product for search?
No. Google's merchant listing structured data docs list both fields as recommended properties of Offer, not required ones. The required set is small: name, image, offers on the product, plus a price and currency inside the offer.
Search Console's rich result reports reflect that split directly: critical issues sit in a table called "Why items are invalid," non-critical ones in "Improve item appearance." Google's own definition: "A valid item is an item that doesn't have any critical issues and can appear on Google as a rich result." A product flagged only for these two fields stays valid and eligible.
So the real cost is narrower than the warning implies: the client loses the delivery/returns annotation Google can attach to the listing, the kind sitting under a competitor's product. That's a different failure class from a block Google can't parse at all, which gets discarded outright along with everything inside it.
If I paste the markup into the theme, why does nothing change on the listing?
Because Google reads shipping and returns data from four sources, in a fixed order of precedence, per its docs for return policy and shipping policy structured data:
- Content API for Shopping account-level settings
- Settings in Merchant Center or Search Console
- Product-level merchant listing markup (the snippet you'd paste into the theme)
- Organization-level markup
Google states the consequence directly: "if you provide both return policy markup on your site and return policy settings in Search Console, Google will only use the information provided in Search Console." The shipping docs say the same about shipping.
If the store is connected to Merchant Center, which the Google & YouTube sales channel requires, level 2 is already occupied, and the theme edit only reaches level 3. It's not wasted work: it clears the Search Console warning, it helps any engine reading the page directly, and it's the only option for a store with neither Merchant Center nor Search Console settings configured. But it explains a pattern that looks like a bug and isn't one: the snippet goes in, the warning clears, and the actual listing doesn't move.
Where does Google actually want this markup, if you implement it at all?
Not where most implementations put it. Google's own recommendation, repeated for both fields on the merchant listing page: "We recommend you provide a global shipping policy for your business under Organization markup instead." A standard, business-wide policy belongs in Organization markup (ShippingService under hasShippingService, MerchantReturnPolicy for returns); per-product Offer markup exists to override that default for a product that genuinely differs, and it supports a smaller set of properties than the Organization-level version.
The common Shopify pattern, a shipping block duplicated into every product page by a Liquid loop, is the exception case implemented as the default, at the lowest-priority level.
There's also a newer path, from Google's November 2025 post "More ways to share your shipping and returns policies with Google": set the policies directly in Search Console under Settings > Shopping > Shipping and returns. Its help page states "You don't need to create a Merchant Center account to configure shipping and returns," but also that policies "created or edited in Merchant Center won't be available in Search Console, and can only be managed in Merchant Center." Once a client connects the Google & YouTube channel, that door closes behind them.
Why does the shipping rate Google shows differ from what the client charges?
This is the Shopify-specific trap underneath the fix, documented by Shopify rather than Google. From the Google & YouTube channel requirements: "Only shipping rates in your General shipping profile can sync to Google Merchant Center. If you use custom shipping profiles, then rates will sync incorrectly with Google Merchant Center and cause errors."
Custom shipping profiles are ordinary store maintenance: one for oversized items, one for a dropshipped line, one for a supplier with different delivery times. The moment a product moves into one, the rate Google receives stops matching checkout. Nothing is technically wrong in the store; the rates just aren't the ones being synced.
Worth checking before touching anything else: Merchant Center can infer a return policy without an explicit upload, shown as a "For most items" annotation. A client's listings may already display returns info while Search Console keeps flagging the field missing from theme markup, this whole article's argument compressed into one symptom.
How do you audit this on a client's store in five minutes?
-
Read the actual markup. View source on a product page, find the JSON-LD block, look inside
offers. Six properties and nothing about shipping or returns means the store is on the stock filter output. - Check which table the warning sits in. Merchant listings report in Search Console: "Improve item appearance" means non-critical; "Why items are invalid" means something else is broken.
- Find the authoritative source. Is a Merchant Center account connected via the Google & YouTube channel? Is there already a policy under Settings > Shopping > Shipping and returns in Search Console? Whichever exists outranks theme markup.
- Compare the synced rate against the real one. Check Merchant Center's shipping settings against what a shopper is actually charged at checkout for the same product and destination.
- Check the Shopify shipping profiles. Settings > Shipping and delivery. Any product sitting in a custom profile instead of General is a rate Google isn't receiving correctly.
What's the actual fix, and does it depend on the client's setup?
If they use the Google & YouTube channel: set shipping and returns in Merchant Center and treat it as the source of truth. Keep the rates Google needs in the General shipping profile, or enter them manually in Merchant Center instead of importing (safer once custom profiles exist). Theme markup on top of this is cosmetic, not the fix.
If there's no Merchant Center account: use Settings > Shopping > Shipping and returns in Search Console. It outranks markup and is far less fragile than theme code.
If you're implementing the markup anyway (for engines reading the page directly, or because neither of the above exists): declare the standard policy once, sitewide, in Organization markup, and reserve per-product Offer markup for genuine deviations. Whatever gets declared has to match what the client actually charges. A hardcoded "free shipping" block on a store that charges for shipping is worse than the original warning: it's a promise to the shopper that checkout then breaks.
Why does this keep coming back after you close the ticket?
Four sources, a documented precedence order between them, and no single screen that tells you which one Google used today. A theme update replaces the file holding the markup. A new supplier shows up and someone creates a custom shipping profile. Someone edits rates in Merchant Center and the store's live rates drift from them. Each change is routine, done correctly, in isolation, and none of it throws an error in Shopify admin.
Worth flagging to clients as ongoing monitoring rather than a one-time fix: a product missing shippingDetails and hasMerchantReturnPolicy is informational and still sellable, but worth a periodic recheck instead of a single pull request.
Have you run into this on a client's store, and did fixing Merchant Center settings actually change what shoppers saw, or just clear the warning? What caught it for you, a client complaint or a routine audit?
The full version of this article — with screenshots and ongoing updates — lives on the StoreCanary blog.
Top comments (0)