Product snippets and merchant listings: two sets of rules
Google reads Product markup for two kinds of result, and each has its own required properties. Product snippets are the rating, price and stock details under an ordinary result; they apply to any page about a product, including review pages. Merchant listings are the richer shopping results, and they apply only to pages where a shopper can buy the product from you.
A store page that meets the merchant listing rules almost always meets the snippet rules too, so build to the stricter set. As of October 2026 both result types are live, and the Rich Results Test reports them as two separate items for the same Product.
| Rule | Product snippet | Merchant listing |
|---|---|---|
| Required on the Product | name, plus at least one of offers, review or aggregateRating | name, image and offers |
| Required on the offer | price | price and priceCurrency |
| priceCurrency | Recommended | Required |
| Worth adding | aggregateRating, review, availability, priceValidUntil | brand, description, a GTIN, MPN or SKU, availability, itemCondition, shipping and returns, ratings |
| Search Console report | Product snippets | Merchant listings |
What the generator outputs
The generator builds one Product. Name, description, SKU, GTIN, MPN and images map straight across, and blank fields are left out rather than printed empty. Brand becomes a Brand item with a name. Images become a list, one URL per line.
The offer has rules. It is printed only when Price and Currency are both filled in and the price is a plain number. Fill in one without the other, or type a price the generator can’t read as a number, and the whole offer disappears from the output with no error message. The currency is converted to capitals. Availability and condition are written as full schema.org URLs, such as https://schema.org/InStock, the form Google’s examples use. The Product URL goes on the offer as its url.
The rating has rules too. It is printed only when both Rating value and Review count are filled in, the rating is above 0 and no higher than 5, and the count is at least 1. A fractional count is rounded down. Outside those limits, aggregateRating is left out.
The GTIN goes in the general gtin property, which takes 8, 12, 13 or 14 digit codes. The generator doesn’t check the digits, so paste the code exactly as it appears on the packaging or in your supplier’s data.
Output from the form’s sample values (description left out for space)
{"@context": "https://schema.org", "@type": "Product", "name": "Merino Wool Crew Sock", "image": ["https://example.com/images/merino-sock.jpg"], "sku": "NF-SOCK-001", "brand": {"@type": "Brand", "name": "Northfell"}, "offers": {"@type": "Offer", "price": "24.00", "priceCurrency": "USD", "availability": "https://schema.org/InStock", "itemCondition": "https://schema.org/NewCondition", "url": "https://example.com/product/merino-sock"}, "aggregateRating": {"@type": "AggregateRating", "ratingValue": 4.6, "reviewCount": 112}}
The tool wraps this in a script tag. Every value here is a placeholder to replace.
Price and currency: why the offer disappears
If your output has no offers block, check the price first. The generator needs a number it can read. A currency symbol, a thousands separator or a comma as the decimal mark all make the price unreadable, and the offer is dropped. Google’s rule matches: a plain number with a dot for decimals, and the currency in its own property.
Currency symbol in the price
Before: $24.00
After: 24.00, with USD in the Currency field
Thousands separator
Before: 1,299.00
After: 1299.00
Comma as the decimal mark
Before: 24,90 with currency EUR
After: 24.90 with currency EUR
The price must also match the price on the page. If the product is on sale, mark up the sale price the shopper pays. The currency is a three-letter ISO 4217 code such as USD, EUR, GBP, CAD or AUD. A symbol like $ is shared by several currencies, which is why the field takes a code.
Availability, condition and price expiry
Availability is the field that goes stale. A template that prints InStock for every product keeps telling Google an item is available after it sells out, and shoppers who click land on an out-of-stock page. If you write markup by hand, update it when stock changes. Better, don’t write it by hand: let your store print availability from its stock data.
The generator offers InStock, OutOfStock, PreOrder, BackOrder, Discontinued and LimitedAvailability. Use PreOrder for items you take orders for before release, and BackOrder for items you sell now but ship later. Schema.org defines a few more values, such as SoldOut and InStoreOnly, which you can type in by hand.
Condition defaults to NewCondition. Change it for used, refurbished or damaged stock. Google recommends itemCondition for merchant listings, and Merchant Center uses it as well.
Price valid until is optional, and easy to get wrong. Google may not show your product snippet when priceValidUntil is a date in the past. Set it only for a price that really ends, such as a sale with an end date, and remove it or move it forward when the sale is over.
Ratings and reviews you are allowed to mark up
Google’s review snippet rules allow aggregateRating on a product page when the ratings come from your customers and the visitor can see them on that page: the average, the count, and the reviews or a link to them. Marking up ratings that aren’t visible, that come from another site, or that nobody gave is structured data spam and can lead to a manual action.
The generator assumes a five-point scale. If your reviews use a ten-point scale, it drops a rating like 8.4, so either convert the average to five points or write the block by hand with bestRating 10. reviewCount is the number of reviews; ratingCount, which you can add yourself, counts every rating, including those without a written review.
Individual reviews go in review, as Review items with an author, a reviewRating and a datePublished. The generator doesn’t build them. Sites that review products rather than sell them can also add positiveNotes and negativeNotes for the pros and cons of an editorial review.
Check for a reviews app before adding a rating at all. Many review plugins and apps print their own aggregateRating for the product, and a second rating in your block, with a different count, contradicts it.
What the generator leaves out
None of these is required for a valid result, but each one is something Google documents for products. Add the ones that apply to your page.
- Shipping details: shippingDetails on the offer, or a shipping policy described once for your organization.
- Return policy: hasMerchantReturnPolicy on the offer, or once at the organization level.
- Variants: a ProductGroup with hasVariant and variesBy for products sold in sizes or colors, each variant a Product with its own offer.
- Price ranges: an AggregateOffer with lowPrice and highPrice when several sellers offer the product. It counts for product snippets only, and Google says not to use it for variants.
- Sale pricing: a priceSpecification that states the regular price alongside the sale price.
- Individual reviews, a seller, and properties such as color, size, material and audience.
If you sell online, start with shipping and returns, since those details can appear in merchant listings. Google accepts them per offer or once for the organization, and the organization route saves repeating the same policy on every product.
WooCommerce already prints Product markup
If your product pages run on WooCommerce, they already have Product markup. WooCommerce core prints it on every single product page that has a price or reviews, from the product’s own data: name, description, image, SKU, URL, an offer with price, currency and stock status, a seller, and the rating and reviews when reviews are on. A variable product whose variations cost different amounts gets an AggregateOffer with the lowest and highest price.
Recent versions also have a GTIN, UPC, EAN or ISBN field on the product’s Inventory tab, printed as gtin when the value is a valid 8, 12, 13 or 14 digit code. WooCommerce fills priceValidUntil on its own: the sale end date when a scheduled sale is running, otherwise a date at the end of the following year.
So don’t paste this generator’s block on a WooCommerce product page. You would get two Product items, and when the price or stock changes, WooCommerce updates its own and yours goes stale. Fix the gaps at the source instead.
- 1.Test the product URL in the Rich Results Test and note what is missing, such as brand or a GTIN.
- 2.Fill in the product’s own fields first: the product image, SKU, the GTIN field, sale dates.
- 3.Add brand through WooCommerce’s Brands feature if your version has it, or through an SEO plugin that adds brand to the markup.
- 4.If you use Yoast WooCommerce SEO or Rank Math, check their product schema settings, since both can extend or replace WooCommerce’s markup.
- 5.Test again and confirm the page still has exactly one Product item.
Where the generator’s output belongs
The output is for product pages nothing else marks up: a single-product landing page built in a page builder, a product sold through a hosted checkout link or an external cart, a page reviewing a product you don’t sell, or a store built on something other than WooCommerce.
On those pages, paste the block into a Custom HTML block on the product page, or print it in that page’s head with a code snippets plugin. Rank Math’s Schema tab and SEOPress PRO’s Structured Data Types metabox both offer a Product type you can fill in per page instead, which keeps the data in fields an editor can update without touching code.
Whichever route you take, the markup is now a copy of facts that change. Put the page on a list and update the block whenever the price, stock or rating changes. A hand-written block with last month’s price tells Google something your page no longer says.
Worked example: a single-product landing page
An illustrative case. A small brand sells one cast-iron skillet from a landing page built in a page builder. Checkout runs through a hosted payment link, so there is no WooCommerce and no Product markup. The page shows a price of $89, an In stock label, and a 4.8 average from 37 reviews.
The first attempt copied the price as displayed, $89, and the output had no offers block. With 89.00 in the Price field and USD as the currency, the offer appears. The reviews are visible on the page, so a rating is allowed, but the source shows the reviews app already prints an aggregateRating, so the rating fields are left blank to avoid a second one.
The offer, before and after the price fix
Before: Price field: $89. Output: no offers block
After: "offers": {"@type": "Offer", "price": "89.00", "priceCurrency": "USD", "availability": "https://schema.org/InStock", "itemCondition": "https://schema.org/NewCondition", "url": "https://example.com/skillet"}
Illustrative values.
The Rich Results Test then reports a valid product snippet and a merchant listing with warnings for the missing shipping and return details. The brand added its flat shipping rate and 30-day return policy to the offer by hand, because those details can show in merchant listings.
Checking Product markup in Google’s tools
Test the live URL in the Rich Results Test. A page about a product should show a Product snippet item, and a page that sells it should also show a Merchant listing item. Errors stop that result type; warnings list recommended properties you left out.
After Google recrawls, Search Console’s Product snippets and Merchant listings reports group your product URLs by issue. A template mistake shows up as the same issue on many URLs at once. Fix it, then use Validate fix in the report so Google rechecks those pages.
If you also use Google Merchant Center, it can update the price, availability and condition of your products from this markup. That only helps when the markup and your feed agree, which is one more reason to print the markup from live store data rather than by hand.
Troubleshooting Product markup by symptom
Start from what the generator or the Rich Results Test shows. Most product markup faults are a missing value, a duplicate source or a value that went stale.
| Symptom | Likely cause | Fix |
|---|---|---|
| No offers in the generator output | Price or currency is blank, or the price has a symbol or separator. | Enter a plain number and a three-letter code. |
| No aggregateRating in the output | A rating field is blank, the rating is 0 or above 5, or the count is below 1. | Enter both values within those limits. |
| Missing field "image" | No image URL. | Add at least one crawlable image URL. |
| Two Product items in the test | WooCommerce or an SEO plugin already prints one. | Remove the pasted block. |
| Two different ratings for one product | A reviews app prints its own aggregateRating. | Keep the app’s and remove yours. |
| The snippet disappeared after a sale ended | priceValidUntil is in the past. | Remove it or set a new date. |
| Search shows In stock for a sold-out item | Hand-written availability wasn’t updated. | Update it, or print it from stock data. |
FAQ about the Product Schema
Do I need both price and currency?
Yes, together or not at all. Merchant listings require both, and a price without a currency is ambiguous. This tool leaves the whole offer out until both are filled in and the price is a plain number.
Can I add review stars to any product page?
Only where the ratings come from real customers and are visible to visitors on that page. Marking up ratings a visitor can’t see, or that came from somewhere else, is structured data spam and a common cause of manual actions.
What is the difference between product snippets and merchant listings?
Product snippets add ratings, price and stock to an ordinary result and need a name plus an offer, review or rating. Merchant listings are shopping-style results for pages that sell the product, and need a name, an image and an offer with price and currency.
Should I add this to my WooCommerce product pages?
No. WooCommerce already prints Product markup on every product page that has a price or reviews. Adding this block creates a second Product that goes stale when prices change. Fill in the product’s own fields, or use an SEO plugin that extends WooCommerce’s markup.
Do I need a GTIN?
Only if the product has one. Google recommends a GTIN, MPN or SKU so it can match your product across stores, but an invented GTIN is worse than none. Products you make yourself often have no GTIN, so use your SKU.
Why did my product snippet stop showing?
Check the Rich Results Test for new errors first. Common causes are a priceValidUntil date in the past, an offer the template stopped printing, or a duplicate Product from a new plugin. Valid markup still doesn’t guarantee Google shows the snippet.
Can I mark up a product I review but don’t sell?
Yes, for product snippets: a Product with a review or an aggregateRating. Merchant listings are only for pages where shoppers can buy the product from you, so leave the offer out if you don’t sell it.