Product schema markup examples for the four fields almost no store states
Your platform already writes the easy half of your product markup. It does not write the half that decides which store an assistant names when a shopper asks "which one should I buy". Here is the code for that half, one field at a time, with the share of 61 measured stores that state each one.
Measured on a real product page at 82 stores from the Tranco long tail, August 2026; percentages are of the 61 that stated machine-readable product data. Full method and numbers →
Before you paste anything
Two rules that matter more than the code below.
- Never add a second
Productblock. If your page already has one — most Shopify, WooCommerce, PrestaShop and Magento themes emit one — merge these fields into it. Two competing blocks describing one product is worse than a missing field. - Never state a value you do not have. A rating of 4.8 on a product with no reviews is a fake review published under your own name. If you have no barcode, delete the line. A missing field costs you a tie-breaker; an invented one costs you the listing.
Every example below goes inside a <script type="application/ld+json"> block on your product template — not the homepage. Placeholders are highlighted; replace all of them.
1. Rating and review count
This is the single biggest gap we measured, and the one an assistant misses most. Asked to compare two products, a model with no rating on either side has nothing to rank by — so it ranks the third store that has one. aggregateRating sits on the Product, not on the offer.
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "YOUR_REAL_AVERAGE",
"reviewCount": "YOUR_REAL_NUMBER_OF_REVIEWS",
"bestRating": "5",
"worstRating": "1"
}
Do not invent this one. If you have no reviews yet, leave the field out and collect some. Ratings are the field most often faked and the one most easily checked against the reviews on your own page.
2. Returns policy
A returns policy is a tie-breaker in a shopping answer: between two similar products, free 30-day returns is the reason to name one of them. It lives on the Offer, not on the product, and it is machine-readable only as an enumeration — a sentence in your footer does not count.
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"applicableCountry": "YOUR_COUNTRY_CODE",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": YOUR_RETURN_WINDOW_IN_DAYS,
"returnMethod": "https://schema.org/ReturnByMail",
"returnFees": "https://schema.org/FreeReturn"
}
3. Shipping cost and delivery time
The rarest field of the six, and the one shoppers ask about most directly ("what has it here by Friday"). It also lives on the Offer. Handling time is how long before you dispatch; transit time is how long the carrier takes.
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": {
"@type": "MonetaryAmount",
"value": "YOUR_SHIPPING_COST",
"currency": "YOUR_CURRENCY_CODE"
},
"shippingDestination": {
"@type": "DefinedRegion",
"addressCountry": "YOUR_COUNTRY_CODE"
},
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"handlingTime": {
"@type": "QuantitativeValue",
"minValue": YOUR_HANDLING_DAYS_MIN,
"maxValue": YOUR_HANDLING_DAYS_MAX,
"unitCode": "DAY"
},
"transitTime": {
"@type": "QuantitativeValue",
"minValue": YOUR_TRANSIT_DAYS_MIN,
"maxValue": YOUR_TRANSIT_DAYS_MAX,
"unitCode": "DAY"
}
}
}
4. A product identifier
The least glamorous field and the one that does the quiet work: it is how an assistant knows your listing and a competitor's listing are the same physical product. Without it you are not compared, you are just another page. Identifiers sit on the Product.
"gtin13": "YOUR_13_DIGIT_BARCODE",
"mpn": "YOUR_MANUFACTURER_PART_NUMBER",
"sku": "YOUR_OWN_INTERNAL_CODE",
"brand": {
"@type": "Brand",
"name": "YOUR_BRAND_NAME"
}
All four in one block
If your product page has no Product markup at all, this is the whole thing — the two fields your platform normally supplies plus the four it does not.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "YOUR_PRODUCT_NAME",
"image": "YOUR_PRODUCT_IMAGE_URL",
"description": "YOUR_PRODUCT_DESCRIPTION",
"sku": "YOUR_OWN_INTERNAL_CODE",
"gtin13": "YOUR_13_DIGIT_BARCODE",
"mpn": "YOUR_MANUFACTURER_PART_NUMBER",
"brand": { "@type": "Brand", "name": "YOUR_BRAND_NAME" },
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "YOUR_REAL_AVERAGE",
"reviewCount": "YOUR_REAL_NUMBER_OF_REVIEWS"
},
"offers": {
"@type": "Offer",
"url": "YOUR_PRODUCT_PAGE_URL",
"price": "YOUR_PRICE_AS_A_NUMBER",
"priceCurrency": "YOUR_CURRENCY_CODE",
"availability": "https://schema.org/InStock",
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"applicableCountry": "YOUR_COUNTRY_CODE",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": YOUR_RETURN_WINDOW_IN_DAYS,
"returnMethod": "https://schema.org/ReturnByMail",
"returnFees": "https://schema.org/FreeReturn"
},
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": {
"@type": "MonetaryAmount",
"value": "YOUR_SHIPPING_COST",
"currency": "YOUR_CURRENCY_CODE"
},
"shippingDestination": {
"@type": "DefinedRegion",
"addressCountry": "YOUR_COUNTRY_CODE"
},
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"handlingTime": { "@type": "QuantitativeValue", "minValue": 0, "maxValue": 1, "unitCode": "DAY" },
"transitTime": { "@type": "QuantitativeValue", "minValue": 1, "maxValue": 5, "unitCode": "DAY" }
}
}
}
}
</script>
Where it goes
- Product template, not the homepage. An assistant comparing products opens the product page. Homepage markup describes your company; it never wins a "which one should I buy" answer.
- Rendered in the HTML, not injected by JavaScript after load. Some AI crawlers do not run scripts. If your data only appears after hydration, assume it is not there.
- One block per product. Variants belong in one
ProductGroupwithhasVariant, or one product with a price range — not five loose blocks. - Check after you ship it. Open the page source and search for
ld+json. If your theme escapes quotes or your CMS strips<script>tags, the block is on the page and still invalid.
Reference: every property here is defined by schema.org/Product and schema.org/Offer. We use them because that is the vocabulary product pages already speak — the same markup that has fed shopping results for a decade is what a crawler for an assistant finds today. What we cannot tell you, and neither can anyone else, is how a given model weighs them internally: that is not published. What is measurable is whether the data is there at all, and for most stores it is not.
Skip the placeholders
Paste your store URL. We open a real product page on your site, count these six fields, and hand back the same markup already filled in from your own page — only for the fields you are missing. Free, no email, a few seconds.
Check my product page →