Product schema markup: rich results with price and reviews
Product, Offer and AggregateRating done right: a JSON-LD example, required fields, price and availability kept consistent, plus how to test it.

Contents
Price, availability and stars in a search result do not happen by themselves: Google has to be able to read those values unambiguously from your page. That is what structured data is for. This article shows which properties a product needs, what the JSON-LD actually looks like, where stores typically get it wrong, and how to verify the result.
What structured data does and does not do
Structured data is an extra block in your source code that describes the visible content of the page a second time, in machine-readable form. The usual format is JSON-LD, a script block in the HTML. The vocabulary comes from schema.org; the rules for how it appears in search come from Google.
Two things that save you disappointment:
- Markup makes a page eligible, it does not force an appearance. Whether Google shows price or stars is decided per query.
- Structured data is not a ranking lever in the narrow sense. It changes how your result looks. That affects click-through rate, not position directly.
The benefit is still concrete: a result with price, availability and a rating average takes up more space and answers a question the searcher would otherwise have to click to resolve.
Product snippet or merchant listing?
Google does not split product markup into "simple" and "detailed". It splits by the purpose of the page, and that distinction decides which properties you owe.
Product snippets are for pages where the visitor cannot buy directly: category overviews, editorial reviews, comparison pages, roundups. The emphasis is on ratings and reviews. According to Google's product snippet documentation, the only required property is name, plus at least one of offers, aggregateRating or review. An AggregateOffer is acceptable here.
Merchant listings are for pages where a shopper can buy the product from you. Google's merchant listing documentation states that only pages where a shopper can purchase a product are eligible, not pages that link out to other sites selling it. Required are name, image and offers, with price and priceCurrency inside the offer. Here AggregateOffer is not allowed, because Google expects the merchant to be the seller, so you need a real Offer.
For a store, the product detail page is practically always the merchant listing case. Collection pages can carry product snippet markup, but it is optional and rarely worth the maintenance.
Required and recommended at a glance
| Property | Status | Note |
|---|---|---|
name | required in both cases | product name as it appears on the page |
image | required for merchant listings | directly reachable image URL; several aspect ratios (16:9, 4:3, 1:1) recommended |
offers | required for merchant listings | as an Offer, not only an AggregateOffer |
offers.price | required for merchant listings | the current, active offer price as a number, dot as decimal separator, no currency symbol |
offers.priceCurrency | required for merchant listings | three-letter ISO 4217 code, e.g. USD |
offers.priceValidUntil | recommended | the date the price expires; Google notes that a snippet may not show if this date is in the past |
offers.availability | recommended | a value from the schema.org list, e.g. https://schema.org/InStock; the short form works too. One value only |
brand | recommended | as a Brand with name |
sku, gtin13, mpn | recommended | unique identifiers, help with matching |
description | recommended | not marketing filler |
aggregateRating | recommended | only with real customer reviews; ratingValue plus reviewCount or ratingCount |
review | recommended | author and reviewRating are required if the block is present |
offers.itemCondition, offers.url | recommended | condition as a schema.org value, URL of the purchasable page |
shippingDetails, hasMerchantReturnPolicy | recommended | shipping and return information; for the standard case they belong at organization level, in the offer only for product-specific exceptions |
JSON-LD example for a product page
The block goes in the <head> or at the end of the <body>. The values below are an example; replace them with your real data.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Trailhead 32 L Hiking Backpack",
"image": [
"https://www.example-store.com/images/trailhead-32-front.webp",
"https://www.example-store.com/images/trailhead-32-side.webp"
],
"description": "Hiking backpack with 32 litres of volume, a ventilated back panel and a rain cover.",
"sku": "TRH-32-BLK",
"gtin13": "4012345678901",
"brand": {
"@type": "Brand",
"name": "Trailhead"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.6",
"reviewCount": "38",
"bestRating": "5",
"worstRating": "1"
},
"offers": {
"@type": "Offer",
"url": "https://www.example-store.com/trailhead-32-hiking-backpack",
"price": "129.00",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock",
"itemCondition": "https://schema.org/NewCondition",
"priceValidUntil": "2026-12-31"
}
}
</script>
Four details that are frequently wrong. The price is a number with a dot, not $129.00. Availability is a value from the schema.org list, either https://schema.org/InStock or the short form InStock, never the words "in stock". ratingValue and reviewCount have to match what visitors actually see on the page. And priceValidUntil has to be generated from your data with a date in the future: Google notes that a product snippet may not show when that date has passed, which is exactly what happens when the value is hardcoded in a template once and then forgotten.
For products whose variants have different prices, you have three options: one marked-up URL per variant, an AggregateOffer with lowPrice and highPrice (product snippets only), or the ProductGroup type Google documents for variants, with productGroupID, variesBy and hasVariant, where each variant can instead point back with inProductGroupWithID.
Keeping price and availability consistent
This is the most common reason enhanced appearances disappear again. The rule is simple: what is in the markup has to be visible and current on the page.
Critical spots in day-to-day store operation:
- Variants. If the visitor picks another size and the price jumps, the markup has to follow, either per variant URL, via
AggregateOfferwith a range, or viaProductGroup. - Promotions. A price hardcoded in the template does not survive a sale. The value has to come from the product database.
- Sold out. Do not report
InStockwhen the item isOutOfStock. Wrong values are how you lose the appearance. - Tax and shipping. The price in the markup is the same one the customer sees. Do not add or subtract anything.
- No markup for invisible content. Only mark up what is actually on the page. Values with no on-page counterpart are a policy violation.
If you also send product data to Merchant Center, feed and markup should carry the same values. Contradictions between them produce disapprovals and price warnings; how to diagnose those is in the article on Merchant Center price mismatches, and how the feed should be set up in the Merchant Center setup walkthrough. This is also why automatic item updates in Merchant Center work better on well-marked-up pages: Google reads your markup instead of guessing at the page.
Variants, filters and parameter URLs tie the topic to canonicals. If several URLs show the same product, the markup belongs on the canonical page; the rest of that discussion is in the articles on duplicate content in ecommerce and on Shopify product variants.
Reviews: the rules that actually matter
Stars in a search result are attractive, which is exactly why the requirements are strict. Reviews have to come from real customers and be visible to visitors on the page. Not allowed:
- reviews you wrote yourself or invented,
- purchased reviews and review packages,
- reviews collected from other sources and presented as your own,
- marking up an average that appears nowhere on the page.
Invented or purchased reviews violate the policies whether or not anyone notices, and the store carries the risk: Google can remove the enhanced appearance for the affected pages or issue a manual action for spammy structured markup. There are also consumer-protection rules outside Google that apply to review handling, and those are a question for your lawyer, not for an SEO article.
The honest route is unspectacular and works: ask for a review after purchase, keep the path short, leave critical reviews up and answer them.
One technical note: aggregateRating with reviewCount: 0, or without any reviews behind it, produces errors. Only output the block once real reviews exist, and leave it off new products.
Step by step: implement and verify
- Check what you already have. Open the Rich Results Test and enter a product URL. Many platforms and themes already ship markup, often incomplete or duplicated.
- Find duplicates. If the theme and an app both output
Product, Google sees two contradictory statements. Pick one source and switch off the other. - Add the missing properties. If
brand,sku,availabilityorpriceValidUntilare missing, fill them from the product data in the template, not by hand. - Test four page types. A simple product, a product with variants, a sold-out product, a product on sale. Those four catch nearly every error.
- Watch the rendering. If the markup is created by JavaScript, use URL inspection in Search Console to confirm it is present in the rendered HTML.
- Ship and wait. After launch it takes days to weeks before Google has re-processed the pages.
- Watch Search Console. The product snippet and merchant listing reports list errors and warnings grouped by cause. Errors first, warnings after.
- Re-check after every theme or app update. Markup has a habit of silently disappearing during updates.
Quick check before launch
- One single
Productblock per product page, no second one from an app - Price as a number with a dot, currency as an ISO code
- Availability as a schema.org value matching real stock, one value only
- Image URLs absolute and publicly reachable, not behind a login or hotlink protection
aggregateRatingonly with real reviews that are visible on the page- Values come from the database, not from the template
- Rich Results Test clean for all four test pages
- Markup present in the rendered HTML, not only visible in the browser
In ecomando the SEO audit checks product pages for missing or contradictory product markup, so you do not have to push every template through the Rich Results Test by hand. It does not replace the test, it just saves the searching.
Structured data belongs to the class of tasks you solve properly once and then only monitor. One caveat worth keeping in mind: a rich result on a page that takes six seconds to become usable does not help much, so work the markup and the Core Web Vitals of your store in the same pass. The overall picture, and where markup sits within it, is in the guide to ecommerce SEO.
Frequently asked questions
What is product schema markup?
Product schema markup is machine-readable data in a page's source code, usually as JSON-LD, describing a product: name, image, brand, price, currency and availability. It lets search engines read those values unambiguously instead of guessing them from the page text.
Which properties does a product need at minimum?
A product snippet requires only name, plus at least one of offers, aggregateRating or review. A merchant listing, meaning a page where someone can buy, requires name, image and offers, and inside the offer both price and priceCurrency.
Does markup guarantee stars in the search results?
No. Correct markup makes a page technically eligible for enhanced appearances, but Google decides per query whether and how to show them.
Can I write or buy my own reviews?
No. Reviews have to come from real customers and be visible on the page. Invented or purchased reviews violate Google's policies and can cost you the enhanced appearance or trigger a manual action.
How do I check whether my markup works?
Test individual URLs with Google's Rich Results Test. For the state of all your pages, use the product snippet and merchant listing reports in Search Console.


