Return Policy Schema: Shipping and Returns on Google

"Return policy schema" is not one tag you drop into your product markup. It is two related but separate schema.org types, MerchantReturnPolicy and OfferShippingDetails, that tell Google how long a customer has to send something back, who pays for the label, and how many days delivery takes, so that information can render directly under a product result instead of staying buried on a policy page three clicks away. If you have already marked up your catalog with schema.org Product, this sits on top of it: Product markup tells Google what you are selling and for how much, this markup tells it what happens after the sale.
The two types behave differently in one important way. MerchantReturnPolicy can be declared once, attached to your Organization markup, and every product inherits it. OfferShippingDetails has no equivalent shortcut. Because shipping cost and delivery time genuinely vary by product weight, warehouse and destination, Google expects it attached to each Offer individually. Knowing which shortcut exists and which one was never built saves you from either duplicating a return policy across five hundred products, or trying to hang OfferShippingDetails on Organization, which this type does not support.
What Shows Up, and Where
The visible payoff is a short line under a product result, something like a free-returns note or an estimated delivery window, on the surfaces Google styles as product listings: product snippets in regular Search and the Shopping tab. It does not appear on an ordinary blue-link result, because ordinary results have nowhere in the layout to put it.
Marking this up makes a page eligible for that treatment; it does not force Google to display it, the same way structured data works for any rich result. If the same offers also run through a Google Merchant Center feed, the feed's account-level settings take precedence on the surfaces that feed serves, and this markup becomes the fallback elsewhere. With no feed at all, it is the only signal Google has.
Two Attachment Points: Offer and Organization
Before writing a single property, decide where the block lives, because the two attachment points solve different problems.
Offer-level MerchantReturnPolicy, nested inside each Offer alongside its price and availability, is the right choice whenever policy genuinely varies: a final-sale clearance line, a made-to-order product with no returns, a perishable item, or a hazardous item that cannot legally travel back by mail.
Organization-level MerchantReturnPolicy, nested inside the same Organization block that already carries your logo and social profiles, is the right choice when one policy is genuinely true for the whole catalog, and it means writing the block once instead of templating it into every product page.
The two combine cleanly. Declare a general policy at the Organization level as the default, then attach a narrower Offer-level policy only to the products that are exceptions. The specific block overrides the default for that one Offer; everything else quietly inherits the sitewide version.
Do not declare an Organization-level default and then override it on most of the catalog anyway. At that point the sitewide block is dead weight, and the real policy should just move to the Offer level everywhere.
The MerchantReturnPolicy Properties That Matter
returnPolicyCategory is the pivot property: it is one of four fixed values, and the value chosen determines what else the policy needs to include. MerchantReturnFiniteReturnWindow requires merchantReturnDays, an integer, because "finite" is meaningless without a number attached to it. MerchantReturnNotPermitted needs no days field, but is worth declaring explicitly on products that truly cannot be returned, since an absent policy reads to Google as unknown, not as no. MerchantReturnUnlimitedWindow also needs no days field. MerchantReturnUnspecified is an escape hatch for when a store genuinely has not decided yet, and it earns little in return for using it.
Past that pivot, the properties that actually change what a shopper sees are the ones most stores skip, since a policy that only sets the category and a day count is technically valid but changes nothing a shopper actually notices at checkout:
- applicableCountry takes one or more ISO country codes, so a store receiving returns at different addresses per country needs a separate policy block for each one, with its own days and method.
- returnMethod states how the item comes back: by mail, in store, or at a kiosk, and a policy can list more than one method at once.
- returnFees states who pays: a free return, customer-responsibility fees, a restocking fee, or a non-refundable original shipping charge, and these can combine, such as a free return label paired with a restocking fee.
- returnShippingFeesAmount is a MonetaryAmount for a flat return-shipping charge, used when returns are not free.
- restockingFee is either a MonetaryAmount or a plain number read as a percentage of the item's price.
- refundType distinguishes a full refund, store credit, or an exchange only, which changes what a customer actually gets back.
- itemDefectReturnFees and customerRemorseReturnFees split "it arrived broken" from "I changed my mind," which matters because many stores waive fees for the former and charge them for the latter, and without the two kept separate that distinction has nowhere to live in the markup.
- returnLabelSource states whether the label ships inside the box, gets downloaded and printed, or is the customer's own responsibility to source.
The OfferShippingDetails Properties That Matter
Shipping markup answers two questions before checkout: what will this cost, and when will it arrive. It nests inside shippingDetails on each Offer, alongside price and the return policy block.
- shippingRate is a MonetaryAmount, a value plus a currency, for a flat rate tied to one destination.
- shippingDestination is a DefinedRegion: a country code, and optionally a narrower region or a postal code range, so a domestic rate and an everywhere-else rate can coexist as two separate blocks under the same Offer.
- deliveryTime is a ShippingDeliveryTime object built from smaller pieces, handlingTime and transitTime, each a minimum and maximum number of days; handlingTime covers how long an order sits before it leaves the warehouse, transitTime covers how long the carrier takes once it has left, and splitting the two matters because a single number like "delivery in five days" hides whether that is mostly packing time or mostly carrier time, which is exactly the ambiguity a shopper deciding between two sellers is trying to resolve.
- cutoffTime and businessDays set when the handling clock actually starts, so an order placed after the daily cutoff, or on a day the warehouse does not operate, correctly starts counting from the next business day instead of the day it was placed.
- doesNotShip is a boolean set true on a region a store explicitly will not serve, useful when a product page is visited internationally but one specific product cannot ship there at all.
- weight, width, height and depth on the offer only matter when rates come from a carrier's tiered table rather than a flat number, letting the calculation match real tiered pricing instead of guessing one flat figure for every order.
Where the Two Blocks Actually Live
In JSON-LD terms, both blocks nest inside the same Offer object that already carries price and availability from your product markup. hasMerchantReturnPolicy and shippingDetails sit as siblings to price and priceCurrency, inside that Offer, not inside the parent Product.
This distinction matters most on a product with variants. A shirt sold in three sizes is, to schema, three separate Offer entries under one Product, each with its own price. A return policy attached only at the Product level, rather than repeated or referenced on every Offer, can end up invisible on the exact variant a shopper actually selects, because Google reads the policy per Offer, not per Product.
- Product contains one or more Offer entries, and each Offer can carry price, priceCurrency, availability, hasMerchantReturnPolicy, and shippingDetails together.
- Organization, declared separately, can carry its own hasMerchantReturnPolicy as the sitewide default, read only where an Offer does not declare its own.
A Worked Example, in Words
Picture a mid-size clothing retailer that ships within one country only. Its Organization-level MerchantReturnPolicy sets returnPolicyCategory to the finite-window value, merchantReturnDays to 30, returnMethod to mail, returnFees to free return, and refundType to full refund: one block, declared once, true for essentially the entire catalog.
Two exceptions get an Offer-level override instead of touching that default. A made-to-order embroidery line sets returnPolicyCategory to not permitted, because a custom item cannot go back into general stock. A clearance category keeps the finite window but changes refundType to store credit only, since deep discounting and a full cash refund are policies most retailers avoid combining.
On the shipping side, every Offer nested under the domestic DefinedRegion carries the same shippingRate and a deliveryTime built from a one-day handlingTime and a two-to-five-day transitTime. A doesNotShip block covers the international DefinedRegion entirely, so a shopper browsing from outside the shipping footprint sees that plainly, rather than discovering it for the first time at checkout.
That is the whole structure: one sitewide default, a small number of named exceptions, and shipping kept deliberately simple because in this example it genuinely is simple. A retailer working with tiered carrier rates or several warehouses would lean harder on the weight and dimension properties instead of a single flat shippingRate, but the Offer-and-Organization shape underneath stays the same.
What Actually Breaks the Display
None of the following throw an error a site visitor would ever see. They just mean the badge silently fails to render, or worse, renders with a wrong number attached to it.
- Setting returnPolicyCategory to the finite-window value without a matching merchantReturnDays fails validation outright, because the category promises a number the markup never actually supplies.
- Entering restockingFee as a flat currency amount when a percentage was intended, or the reverse, does not error, because schema accepts either type without complaint, but a 15 meant as fifteen percent read as fifteen currency units is wrong by an order of magnitude.
- Leaving priceCurrency off a shippingRate means a bare number with no currency is not a valid MonetaryAmount, so the whole shippingDetails block gets dropped rather than partially rendered.
- Writing a category as free text, such as "30 day window," instead of the exact schema.org term, fails silently, because Google's parser matches the literal enumerated string, not the intent behind it.
- Letting a Google Merchant Center feed disagree with this on-page markup for the same product does not just get ignored where a feed exists; the feed's settings win on the surfaces it serves, and the mismatch lowers confidence in the feed itself.
- Declaring an Organization-level default that most products then override anyway leaves the sitewide block serving almost nothing, which defeats the reason to use it at all.
Checking What You Shipped
Google's Rich Results Test validates Product markup and, within that same pass, flags missing or malformed return and shipping fields as warnings even when the core product fields pass clean. A green result there is not confirmation the return and shipping blocks are complete, only that nothing in them broke the test outright. Search Console's structured-data reporting for merchant listings tracks these specific fields over time across your indexed pages, which is where a sitewide template change that silently dropped shippingDetails would actually surface.
Test against a real, representative product rather than the simplest SKU in the catalog. A digital-only item, a final-sale item, or a sized variant will exercise properties a default template may not handle, such as doesNotShip, MerchantReturnNotPermitted, or a per-variant Offer, and those are exactly the pages where a missing property downgrades silently to no badge.
Treat this markup as you would price or availability data: generated from the same system that actually knows the current return window and shipping carrier, not written once by hand and left to drift the next time either one changes.
Frequently asked questions
Do I need a Google Merchant Center account to use this markup?
No. MerchantReturnPolicy and OfferShippingDetails are on-page structured data and work without one. If a Merchant Center feed also covers the same products, its account-level settings take precedence on the surfaces that feed serves, and this markup is what Google reads for product results the feed does not back.
Can I set different return windows for different countries?
Yes. applicableCountry accepts more than one country code, so the MerchantReturnPolicy block can be repeated with a different merchantReturnDays and returnMethod for each country a store accepts returns from, instead of forcing one global number.
What happens if I mark up returns but skip shipping, or the reverse?
Each block validates independently, so Google can show a return badge with no shipping information and the other way around. A shopper comparing two listings side by side tends to favor the fuller one, so it is worth treating the two blocks as a pair rather than a choice.
Does an Organization-level return policy override an Offer-level one?
No, it is the reverse. The more specific block wins: an Offer-level hasMerchantReturnPolicy is read as that product's actual policy, and the Organization-level block only fills in for offers that do not declare their own.
Is microdata as reliable as JSON-LD for this markup?
Google accepts both, but JSON-LD holds up better at this depth of nesting. MerchantReturnPolicy and OfferShippingDetails nest three or four levels deep, such as Offer, shippingDetails, deliveryTime, transitTime, and microdata's attribute-per-element syntax gets error-prone fast at that depth.