The Problem
Logged into Search Console for a WooCommerce client on Friday and the Products report had 4,200 valid items the week before, 0 valid items now, and 4,200 fresh "Invalid" entries. The reason was identical on every URL:
Missing field "hasMerchantReturnPolicy.returnPolicyCategory"
Missing field "shippingDetails"
No code had changed on the site. The Product schema we shipped six months ago — Product with Offer, AggregateRating, price, priceCurrency, availability — was still valid Schema.org. But Google's structured data parser had tightened its requirements during the May algorithmic update, and MerchantReturnPolicy plus OfferShippingDetails are now hard requirements for Product rich results in the US, EU, and UK markets.
If your Product rich results vanished from the SERP in the last week and Search Console is full of "Missing field" warnings, here is the markup you need and how to ship it without rewriting your entire JSON-LD layer.
Why It Happens
Google has been signaling this change since late 2024. The merchant listings documentation moved MerchantReturnPolicy from "recommended" to "required" for sellers in the affected regions, and the parser now rejects Product entities that ship without it. Same story for OfferShippingDetails — return policy and shipping cost are the two fields a customer cares about most, and Google decided rich results should not be possible without both.
The catch is that the warning shows up under hasMerchantReturnPolicy.returnPolicyCategory, which is a nested field, so a lot of devs assume it is a small typo somewhere. It is not. The whole MerchantReturnPolicy block is missing from most Product schema generators, including Yoast, Rank Math, and the WooCommerce default JSON-LD that ships in core.
A second source of confusion: Google accepts the policy at the Organization level too. If you set a single store-wide return policy in your schema once, it applies to every product. Most sites do not do this, so Google looks per-product, finds nothing, and flags every URL.
The Fix
Step 1: Decide where to put the policy. Two options. Per-product schema if your return rules vary (e.g., final-sale items, perishables). Organization-level schema if every product follows the same rules. For 90% of stores, organization-level is correct and saves you from bloating every product's JSON-LD.
Step 2: Add MerchantReturnPolicy to your Organization schema. Drop this once, in your root layout or homepage:
const orgSchema = {
"@context": "https://schema.org",
"@type": "Organization",
"@id": "https://yourstore.com/#organization",
"name": "Your Store",
"url": "https://yourstore.com",
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"applicableCountry": ["US", "GB", "DE"],
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": 30,
"returnMethod": "https://schema.org/ReturnByMail",
"returnFees": "https://schema.org/FreeReturn"
}
}
The four fields that satisfy Google's parser: returnPolicyCategory, merchantReturnDays (when category is FiniteReturnWindow), returnMethod, and returnFees. applicableCountry is required if you sell internationally.
The valid returnPolicyCategory values are exactly three: MerchantReturnFiniteReturnWindow, MerchantReturnNotPermitted, and MerchantReturnUnlimitedWindow. Use the full schema.org URL — Google's parser accepts the bare string but is stricter about it in some locales.
Step 3: Reference the org from each Product. In your Product schema, link to the org's policy with @id so Google can resolve it:
const productSchema = {
"@context": "https://schema.org",
"@type": "Product",
"name": product.name,
"image": product.images,
"description": product.description,
"sku": product.sku,
"brand": {
"@type": "Brand",
"name": "Your Store"
},
"offers": {
"@type": "Offer",
"url": `https://yourstore.com/products/${product.slug}`,
"priceCurrency": "USD",
"price": product.price,
"availability": "https://schema.org/InStock",
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": {
"@type": "MonetaryAmount",
"value": "5.99",
"currency": "USD"
},
"shippingDestination": {
"@type": "DefinedRegion",
"addressCountry": "US"
},
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"handlingTime": {
"@type": "QuantitativeValue",
"minValue": 0,
"maxValue": 1,
"unitCode": "DAY"
},
"transitTime": {
"@type": "QuantitativeValue",
"minValue": 2,
"maxValue": 5,
"unitCode": "DAY"
}
}
},
"hasMerchantReturnPolicy": {
"@id": "https://yourstore.com/#organization"
}
}
}
The @id reference saves you from repeating the full policy on every product page. Google's parser follows the reference when it sees the org schema in the same document or via crawled @id URL.
Step 4: Validate before deploying. Two things to check, in order:
# 1. Run the URL through Google's Rich Results Test
curl "https://search.google.com/test/rich-results?url=https://yourstore.com/products/sample"
# 2. Use the Schema.org validator for spec compliance
curl "https://validator.schema.org/#url=https://yourstore.com/products/sample"
The Rich Results Test is what actually matters for SERP eligibility — it tells you if Google will treat the page as a rich result candidate. The Schema.org validator catches generic structured data errors like missing @context. Run both. The official Google merchant listing requirements page lists every field and its acceptable values.
Step 5: Fix WooCommerce's default schema if you are on WordPress. WooCommerce core still ships Product schema without MerchantReturnPolicy. Override it via a filter in your theme:
add_filter('woocommerce_structured_data_product', function ($markup, $product) {
$markup['offers'][0]['hasMerchantReturnPolicy'] = [
'@id' => home_url('/#organization')
];
$markup['offers'][0]['shippingDetails'] = [
'@id' => home_url('/#shipping-policy')
];
return $markup;
}, 10, 2);
Then add the MerchantReturnPolicy and OfferShippingDetails once to your homepage's Organization JSON-LD, referenced by those @id values. One filter, one source of truth, every product page becomes valid.
The Lesson
Google quietly tightened structured data requirements during the May update and Product schema that was valid in April is silently invalid now. Add MerchantReturnPolicy and OfferShippingDetails once at the Organization level, reference them from each Product offer with @id, and validate with the Rich Results Test before pushing.
If your product rich results have vanished and you would rather have someone fix the schema across thousands of URLs without breaking your existing SEO, this is the kind of work I do — see my services, or check the technical SEO audit checklist for 2026 for the other Search Console gotchas to clear after a Google update.