SEO

Product schema generator (JSON-LD)

By Charles Summers · Updated · Free, no signup

Short answer

One Product block can qualify for two different things and the bars are not the same. A plain product snippet in the blue links needs little beyond a name plus one of offers, review or aggregateRating. A merchant listing, the richer treatment on shopping surfaces, additionally wants an image and a fully specified offer with a numeric price, an ISO 4217 currency, an availability value and a priceValidUntil that has not passed. Identifiers such as gtin and sku decide whether your item can be matched to the same product elsewhere, and inventing an aggregateRating is the fastest route to a site-wide manual action.

Use the schema markup generator

Why product pages need a different approach

Product is the only type here where the data leaves the search result and enters a commercial surface, and that changes the tolerance for error completely. A wrong date on an article is embarrassing. A price in your markup that disagrees with the price in the basket is a consumer-facing discrepancy that Google has both automated and manual machinery for catching, because someone clicked through expecting one number and met another.

That is also why the requirements feel disproportionate compared with every other type on this tool. The offer node is doing most of the work: price as a bare number with no symbol and no thousands separator, priceCurrency as a three-letter code, availability as a schema.org URL rather than the word in stock, and a priceValidUntil that stops a stale price being served indefinitely. Get the offer right and almost everything else is optional detail. Get it wrong and the item is not thin, it is ineligible.

It runs entirely in your browser. Nothing you type is sent to a server, no account is required, and there is no usage limit, because there is no cost per run to control.

Product snippet and merchant listing are two different qualifications

Google splits the Product documentation into two feature families, and the split explains most of the confusion people have about what is mandatory. A product snippet is the treatment you see attached to an ordinary organic result: a price line, a stock status, sometimes a star rating. A merchant listing is the richer, more commercial treatment used on shopping surfaces, where the item is being presented as something purchasable rather than as a page about something purchasable.

The snippet bar is genuinely low. You need a name and at least one of offers, review or aggregateRating, and that is close to it. The merchant listing bar is where the demands appear: an image, and an offer that is complete enough to transact against, meaning price, priceCurrency and availability all present and correct. This is why two teams can read the same documentation and come away with opposite accounts of whether image is required. Both are right, about different features.

Practically, decide which surface you are actually chasing before you argue about completeness. A publisher writing a review round-up wants the snippet and should not pretend to be a merchant. A retailer wants the merchant listing, and for a retailer the markup is not the primary channel anyway: a product feed in Merchant Center is, with the markup on the page acting as corroboration and as the fallback for items the feed misses. When the two disagree, the disagreement itself is the problem, because it is visible to a system comparing them.

Do not overlook the sibling of that rule: the price in the markup, the price in the feed and the price a customer sees on the page all have to be the same number. Currency-switching, geo-pricing and logged-in discounts are the three mechanisms that most commonly break this without anyone noticing, because the marked-up price is generated server-side from a default that the visible page then overrides.

Ratings and reviews are where sites earn manual actions

The star rating is the most attractive element in a product result and it is the one with the most rules attached. The first is the obvious one: an aggregateRating must reflect ratings that genuinely exist and are visible on that page. A block asserting 4.8 from 312 reviews on a page displaying no reviews at all is not an oversight, it is the textbook example in Google's spam documentation, and the consequence lands on the whole site rather than the one URL.

The second rule catches more honest people. Self-serving reviews, meaning reviews about the business or organisation placed on that business's own site, are excluded from review rich results. Marking up testimonials on your homepage as an aggregateRating for your Organization is the common version of this. Rating a product you sell is fine. Rating yourself is not.

The third is subtler and is a template bug rather than a policy breach. Sites frequently emit a site-wide trust score, the aggregate from a third-party review platform covering the whole shop, into the aggregateRating of every individual product. Every product on the site then claims the same 4.7. It is not a per-product rating, it is a company rating in a product-shaped slot, and it is trivially detectable because a thousand distinct SKUs share one number.

  • ratingValue and one of ratingCount or reviewCount are the minimum. A rating with no count behind it is not usable and will be reported as incomplete.
  • bestRating defaults to 5 and worstRating to 1. Only set them if you genuinely use another scale, and if you do, make sure the visible stars on the page use the same scale.
  • Individual Review items need an author. An author of "Anonymous" or the name of your own company is a common reason review items are ignored.
  • Remove ratings when the reviews come down. A page that stops displaying reviews but keeps emitting the aggregate has quietly become invisible markup.
  • Aggregating across variants needs care. If reviews are collected at the product-group level, say so consistently rather than duplicating the same aggregate onto every size and colour URL as though each earned it independently.

Identifiers, variants and the properties that decide matching

Everything discussed so far affects whether your item is eligible. Identifiers affect something different: whether the system can tell that your Fellhead Trail Shell and a competitor's listing of the same garment are the same object. That matching is what puts you into a comparison, and without an identifier you are an island. gtin, in whichever length applies to your market, is the strong one because it is externally assigned. mpn is the fallback for manufactured goods without a barcode. sku is yours alone and is useful internally but proves nothing to anyone outside your business.

Variants were a genuine gap in the vocabulary for years and were addressed by the ProductGroup pattern. The shape is a ProductGroup carrying the shared attributes and a productGroupID, with each purchasable variant as a Product in hasVariant, and variesBy naming the dimensions that differ, typically size and colour. The alternative that most stores actually ship, an independent Product block on every colourway with no relationship expressed between them, is not invalid but it throws away the information that these are one garment in nine forms.

Two more offer-level properties are worth knowing because they can surface as annotations rather than just satisfying a validator. shippingDetails lets you state delivery cost and destination, and hasMerchantReturnPolicy lets you state the returns window. Both are the sort of thing a buyer scans for before clicking, and both are commonly filled in with a generic policy that does not match what the checkout actually applies, which puts you back in the disagreement problem from the first section.

Finally, priceValidUntil. It exists so a price cannot be asserted forever, and a date in the past is treated as an expired offer. Wiring it to a rolling window computed at render time, rather than to a hardcoded date somebody typed during a launch, is a two-line change that prevents an entire catalogue silently ageing out.

Numbers worth knowing

MetricTypicalWhat it means
Product snippet minimumname plus one of offers, review, aggregateRatingThe low bar, and the one most non-retail pages should be aiming at. A publisher marking up a product they do not sell does not need a complete offer.
Merchant listing minimumname, image, and a complete offerComplete meaning price, priceCurrency and availability all present. This is the source of the endless argument about whether image is required: for this feature it is, for a snippet it is not.
Price formatbare number, no symbol215.00 and not £215.00 or 1,215. The currency belongs in priceCurrency as an ISO 4217 code. A symbol inside the price value is one of the most common reasons an offer is rejected.
Expired priceValidUntiltreated as an expired offerA date in the past does not degrade gracefully. Compute it as a rolling window at render time rather than hardcoding a date that ages out of the whole catalogue at once.

Mistakes that quietly cost you results

Emitting your site-wide trust score as every product's aggregateRating
A thousand SKUs sharing one 4.7 is a company rating in a product slot, and it is detectable at a glance. Aggregate per product from reviews shown on that product page, and if you have none, omit the property entirely rather than borrowing one.
Marking up the default currency price on a geo-priced page
If the visitor is shown one number and the JSON asserts another, you have created a mismatch on the one property Google checks hardest. Generate the offer from the same pricing call that renders the visible price, per request, and include the matching priceCurrency.
Treating markup as a substitute for a Merchant Center feed
For a retailer the feed is the primary channel and the markup corroborates it. Running only markup limits what you can express, and running both with different values is worse than running either alone, because the disagreement is what gets flagged.
Shipping nine independent Product blocks for nine colourways
Not invalid, but it discards the fact that these are one garment. Use a ProductGroup with productGroupID, put each purchasable option in hasVariant, and set variesBy to the dimensions that actually differ.

What does the output look like?

This is the exact output the tool produces from the example inputs. It is generated by the same code that runs when you click the button, so what you see here is what you get.

SCHEMA TYPE: Product TARGET PAGE: https://northfell.com/shop/fellhead-trail-shell-womens JSON-LD (paste into the head, or anywhere in the body) <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Product", "name": "Fellhead Trail Shell, Womens", "description": "A 2.5-layer waterproof shell weighing 240g, taped seams throughout and a helmet-compatible hood.", "image": [ "https://northfell.com/img/shop/fellhead-trail-shell.jpg" ], "brand": { "@type": "Brand", "name": "Northfell" }, "sku": "TODO: your internal SKU, must match the one you send to Merchant Center", "offers": { "@type": "Offer", "url": "https://northfell.com/shop/fellhead-trail-shell-womens", "price": "215.00", "priceCurrency": "GBP", "availability": "https://schema.org/InStock", "priceValidUntil": "TODO: ISO 8601 date the price is good until, e.g. 2026-12-31" } } </script> REQUIRED BY GOOGLE FOR PRODUCT [ok] name supplied [ok] offers.price 215.00 [ok] offers.priceCurrency GBP (inferred from the currency symbol you used) [ok] image supplied [ok] offers.availability defaulted to InStock, change it if that is wrong RECOMMENDED (eligible without these, thinner result with them missing) [ok] brand.name taken from the brand field [ok] description supplied [TODO] sku left as a TODO [TODO] aggregateRating only add this if real reviews are visible on the page [TODO] review only add this if real reviews are visible on the page [TODO] priceValidUntil left as a TODO TODO MARKERS LEFT IN THE BLOCK: 2 Search the block for TODO: and replace each one. Every TODO string is currently a literal value, so the JSON parses but the property is wrong. NOTE ON THIS TYPE A plain product snippet needs little more than a name. Merchant listing experiences are the strict case, and that is where image and a fully specified offer become required rather than advisory. BEFORE YOU SHIP 1. Every value above must also be visible to a user on the page. Invisible markup is what the spammy structured data manual action is for. 2. Test the rendered page, not the source, if anything on it is injected by JavaScript. 3. Check the enhancement report in Search Console a week later. Errors mean ineligible; warnings mean eligible but thinner.

Frequently asked questions

Do I need Product markup if I already send a Merchant Center feed?

Yes, and they do different jobs. The feed is authoritative for shopping surfaces and gives you fields markup cannot express. On-page markup covers items missing from the feed, corroborates the feed values, and is what any non-shopping consumer of your page reads. The failure mode is not having both, it is having both with different prices or stock states, because a system comparing them treats the disagreement as a reliability signal about your data generally.

How should out of stock or discontinued products be marked up?

Honestly, with the availability value that matches reality: OutOfStock while it is temporarily unavailable, Discontinued when it is not coming back. Leaving InStock on a sold-out item is the version of this that damages trust fastest, because a click lands on an unbuyable page. If the page is being retired entirely, that is a redirect decision rather than a markup decision, and the markup should not outlive the page it describes.

Can I mark up a price range instead of a single price?

Yes, using an AggregateOffer with lowPrice and highPrice rather than an Offer with a single price, which suits a product sold in several sizes at different prices. Be aware that the richer commercial treatments generally want a specific transactable price, so an aggregate offer tends to qualify you for less than a precise one. If a single variant is the obvious default, marking that variant's exact price usually serves you better than a range.

What actually happens if a competitor reports my ratings as fake?

Reports are one input among many and are not a switch anybody can flip. The mechanism that matters is the spammy structured data manual action, which is applied when markup does not represent the visible content of the page, and which is issued at the site level rather than against the single URL. The recovery path is removing or correcting the markup and filing a reconsideration request, which is slow. It is a bad trade for a star rating.

Does gtin matter for a product only I sell?

Less than it does for a resold product, because there is nothing to match you against. For an own-brand item with a real barcode, supplying the gtin is still worth doing since it establishes the identifier externally rather than leaving mpn and sku as your only references. For a genuinely bespoke or made-to-order item there may be no identifier to give, and inventing one is worse than omitting it.

Related free tools

Some links on this site are affiliate links, which means Hacking Demand may earn a commission if you buy through them at no extra cost to you. This does not influence which tools are listed. The tools on this page are free and have no affiliate relationship of any kind.