· Right On Tech · Search Engine Hacks
Structured Data for Product Pages: The Schema.org Markup That Earns Rich Results

When a product shows up in Google with its price, a star rating, “In stock” and free shipping right in the search result, that extra detail usually comes from structured data: a small block of code on the product page that describes the product in a format search engines understand. It doesn't change how the page looks to visitors, but it can change how your listing looks in search.
This guide covers the schema.org types that matter for product pages, what Google requires, and copy-ready JSON-LD examples. Everything is based on Google's product structured data documentation.
Two kinds of product results
Google distinguishes between two experiences, and the one you're eligible for depends on whether people can buy on your page:
| Product snippets | Merchant listings | |
| For | Pages about a product where people can't buy directly (reviews, comparisons, catalogs) | Pages where customers can buy the product from you |
| Required | name, plus at least one of review, aggregateRating or offers | name, image, and an Offer with a price above zero and a currency |
| Can show | Ratings, reviews, price range, pros and cons (editorial reviews only) | Price, availability, shipping, returns, sale prices, ratings |
If you run an online store, aim for merchant listings. Google notes that including the required merchant listing properties can also make the page eligible for product snippets. See merchant listing and product snippet requirements.
Why JSON-LD
Structured data can be written in a few formats, but Google recommends JSON-LD: a separate script block in the page, so you don't have to weave attributes through your HTML. Most store platforms and SEO plugins generate it automatically; your job is to check that it's complete and accurate.
The core: Product and Offer
Here's a minimal product page that meets Google's merchant listing requirements, plus a few recommended identifiers:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Canvas Low-Top Sneaker, Navy",
"description": "Lightweight canvas sneaker with a cushioned rubber sole.",
"image": [
"https://www.example.com/images/sneaker-navy-1x1.jpg",
"https://www.example.com/images/sneaker-navy-4x3.jpg",
"https://www.example.com/images/sneaker-navy-16x9.jpg"
],
"sku": "CLS-NAVY-42",
"gtin13": "0012345678905",
"brand": { "@type": "Brand", "name": "Example Brand" },
"offers": {
"@type": "Offer",
"url": "https://www.example.com/sneakers/canvas-low-top-navy",
"price": 59.00,
"priceCurrency": "USD",
"availability": "https://schema.org/InStock",
"itemCondition": "https://schema.org/NewCondition"
}
}
</script>
A few things to get right:
- One
Offer, notAggregateOffer, for merchant listings. Google requires anOfferbecause the merchant must be the seller.AggregateOffer(a price range across sellers) is only for product snippets. pricemust be greater than zero, andpriceCurrencyuses a three-letter ISO 4217 code such as USD.- Images: Google recommends several high-resolution images in 16:9, 4:3 and 1:1 ratios.
- Identifiers:
gtin(orgtin8/gtin12/gtin13/gtin14),mpn,skuandbrandare recommended. Use the numeric GTIN, not a URL. - Availability uses schema.org values like
InStock,OutOfStock,BackOrderorPreOrder. Keep it in sync with real stock.

Shipping and returns
Shipping cost, delivery time and return terms are some of the most useful details in a product result. You can add them to the Offer:
"offers": {
"@type": "Offer",
"price": 59.00,
"priceCurrency": "USD",
"availability": "https://schema.org/InStock",
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": { "@type": "MonetaryAmount", "value": 0, "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": {
"@type": "MerchantReturnPolicy",
"applicableCountry": "US",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": 30,
"returnMethod": "https://schema.org/ReturnByMail",
"returnFees": "https://schema.org/FreeReturn"
}
}
shippingRateholds one rate. Use0for free shipping. The currency must match the offer.deliveryTimeuses whole days forhandlingTime(before it ships) andtransitTime(in the carrier's hands).returnPolicyCategoryis one ofMerchantReturnFiniteReturnWindow,MerchantReturnNotPermittedorMerchantReturnUnlimitedWindow. A finite window also needsmerchantReturnDays.
Better: set store-wide policies once
If the same shipping and return terms apply to most products, Google recommends defining them once at the organization level instead of repeating them on every product. Return policies go in hasMerchantReturnPolicy and shipping policies in hasShippingService, nested under your Organization (or OnlineStore) markup, typically on your home page or policy page:
{
"@context": "https://schema.org",
"@type": "OnlineStore",
"name": "Example Store",
"url": "https://www.example.com/",
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"applicableCountry": "US",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": 30,
"returnMethod": "https://schema.org/ReturnByMail",
"returnFees": "https://schema.org/FreeReturn"
}
}
Product-level details then only need to cover exceptions, such as an oversized item with different shipping. You can also set these policies in Google Merchant Center or Search Console; Google documents that settings there take precedence over on-page markup.
Sale prices
To show a sale with the original price crossed out, put the current price in price and the original price in a UnitPriceSpecification with priceType set to StrikethroughPrice:
"offers": {
"@type": "Offer",
"price": 44.00,
"priceCurrency": "USD",
"availability": "https://schema.org/InStock",
"priceSpecification": {
"@type": "UnitPriceSpecification",
"priceType": "https://schema.org/StrikethroughPrice",
"price": 59.00,
"priceCurrency": "USD"
}
}
Don't put a priceType on the active sale price itself. If the sale has an end date, add priceValidUntil or validThrough to the offer.
Ratings and reviews
Star ratings are what most store owners want. Add aggregateRating for the overall score and review for individual reviews shown on the page:
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": 4.6,
"reviewCount": 38
},
"review": [
{
"@type": "Review",
"author": { "@type": "Person", "name": "Example Reviewer" },
"reviewRating": { "@type": "Rating", "ratingValue": 5, "bestRating": 5 },
"reviewBody": "The review text exactly as it appears on the page."
}
]
Google's review snippet guidelines set the rules:
aggregateRatingneedsratingValueplusratingCountorreviewCount.- Each
Reviewneeds anauthorwith a real name (a person or organization, under 100 characters) and areviewRatingwith aratingValue. The scale is assumed to be 1 to 5 unless you setbestRatingandworstRating. - Only mark up reviews visitors can see on the page. Ratings in the markup must match what's displayed.
- No self-serving reviews of your own business. Star snippets aren't shown for
LocalBusinessorOrganizationmarkup when the business controls reviews about itself. Product reviews on your product pages are a different case and are allowed. - Pros and cons (
positiveNotes/negativeNotes) are only for editorial review pages, not merchant product pages or customer reviews.
Product variants
If a product comes in sizes or colors, Google supports product variant markup using ProductGroup with hasVariant, variesBy and productGroupID. Each variant gets its own Product with its own offer, SKU and GTIN. If your store has one URL per variant or uses a variant selector, it's worth implementing.
Testing and monitoring
- Test a page with Google's Rich Results Test. It shows which rich results the page is eligible for and flags errors and warnings.
- Fix errors first (missing required properties), then warnings (missing recommended ones) where you have the data.
- Check Search Console. The Merchant listings and Product snippets reports show valid and invalid items across your whole site after Google recrawls.
- Re-test after theme or plugin updates. A template change can silently remove markup from every product page.
Common mistakes
- Markup that doesn't match the page: a different price, “in stock” when the page says sold out, or ratings that aren't displayed.
- Two plugins both outputting Product markup, creating duplicate or conflicting data.
- Prices in the wrong format (include only the number in
price, no currency symbols or commas). - Using
AggregateOfferon a regular store page and losing merchant listing eligibility. - Missing GTINs on products that have them.
One last expectation to set: Google says that valid structured data makes a page eligible for rich results but doesn't guarantee they'll appear. Accurate, complete markup simply gives Google the best information to work with.
Need it checked?
Right On Tech builds and maintains online stores and can audit your product markup, fix template-level issues, and connect it with your Merchant Center feed so everything matches. If your product results look plain or Search Console is reporting errors, contact us for a review.


