What does Either "offers", "review", or "aggregateRating" should be specified actually ask for?
It asks for one of three things, and it only needs one. Google's product snippet documentation says Product markup needs a name plus at least one of offers, review or aggregateRating. When Search Console flags a product with this error, it's found a Product with a name and nothing it can show beside it. That's a markup problem, the kind I fix through my schema markup services for WordPress, and it's most of what structured data work on a store comes down to.
The error sits in the Product snippets report as a critical issue, so the item it names is invalid. Google's help draws the line plainly: a valid item has no critical issues and can appear as a rich result, and an invalid item can't. Non-critical issues sit in a separate table and don't block anything.
There are two product reports, and they don't ask for the same things. Product snippets covers product pages broadly, including review and aggregator sites. Merchant listings covers pages where a shopper can buy the product, and it's stricter.
| Report | Required on the Product | Required inside the offer | Reviews and ratings |
|---|---|---|---|
| Product snippets | name, plus one of offers, review or aggregateRating | price | Optional, and they must follow the review snippet rules |
| Merchant listings | name, image and offers | price above zero, and priceCurrency | Recommended, never required |
Read the two rows together and the answer's already there. Offers is required by one report and accepted by the other, so on a simple product it's the property that clears both.
Why is offers the honest fix?
Because it's the only one of the three that every shop already has. A product page that sells something shows a price, a currency and whether it's in stock. Marking that up doesn't claim anything new. It repeats, in a format Google reads, what a shopper can already see.
Review and aggregateRating describe what customers said, and a store without reviews has nothing true to put there. So the fast fix and the honest fix are the same fix: a complete offer.
A minimal offer for a simple product looks like this in JSON-LD: "offers": { "@type": "Offer", "price": "49.99", "priceCurrency": "CAD", "availability": "https://schema.org/InStock" }. I make the price match the one on the page, with the same currency the checkout uses, because markup that disagrees with the page is exactly what structured data rules are there to catch.
With a price range on a variable product, WooCommerce prints an AggregateOffer. Product snippets accept it, but Google says not to use AggregateOffer for product variants, and merchant listings need an Offer, so the fix is an Offer per variation or ProductGroup markup with hasVariant. At one price, it prints a single Offer. Neither needs an invented figure; my WooCommerce SEO audit checklist covers the rest.
Why does a WooCommerce store throw this error at all?
WooCommerce's own code is the clue. Core adds offers only when a product's price isn't empty. It adds aggregateRating and review only when ratings are switched on and the product has some. And when all three would be empty, it prints no Product markup at all. So core WooCommerce alone doesn't produce this error, and it's usually one of these four causes.
- i.
A second Product block
An SEO plugin, the theme, a page builder widget or a snippet someone pasted in prints its own Product markup beside WooCommerce's. If that block has only a name, it's the one Search Console flags, and the page carries two Products that disagree.
- ii.
A product with no price
Quote-only and call-for-price products often leave the price field empty. WooCommerce then leaves offers out, as its code says it will, and anything else that marks the page as a Product trips the error.
- iii.
Product markup on the wrong page
A category page, a blog post or a landing page with a Product type on it, usually from a builder template, has no single price to give. Google's product rich results are meant for pages that focus on one product or its variants.
- iv.
Prices hidden by a customisation
Catalogue-mode setups that hide prices from logged-out visitors, or a filter that edits WooCommerce's structured data, can strip offers out without anyone noticing. Staff who are logged in see a normal page, and Googlebot doesn't.
Rank Math and Yoast each answer this error in their own docs. Rank Math builds Product schema from WooCommerce data, and when the error comes from product category pages, its knowledge base points to Titles & Meta, Product categories, Remove Snippet Data. Yoast's 2019 answer is its paid Yoast WooCommerce SEO add-on, which ties WooCommerce's product markup into Yoast's schema graph. Neither setting can give a product a price it doesn't have.
Off WordPress, the same logic holds. Shopify's structured_data Liquid filter outputs a Product, or a ProductGroup when there are variants, with offers in its documented example, so there I look for an app or theme snippet printing a second block. Squarespace generates Product markup itself and says it can't be edited or removed, so the fix lives in the product's own details.
Rather have me do this? One person, whole job.
Book a call on UpworkHow I fix either offers, review or aggregateRating should be specified on WooCommerce
I keep to this sequence. Nothing gets edited until I know which block of markup the error comes from, because removing the wrong one can turn a warning into an error.
- Step 01
Export the affected URLs
In the Product snippets report I open the error and export the example URLs, then sort them by template: simple products, variable products and categories. A pattern shows up quickly, because markup comes from templates. A WooCommerce store with 1,512 URLs in 13 sitemaps can't be fixed page by page, but a template fix reaches every product at once.
- Step 02
Read the rendered markup, not the settings
I run a few of those URLs through the Rich Results Test and the Schema Markup Validator, the pair WooCommerce's own code comments name, and read the JSON-LD in the page source. What counts is the markup the page serves, not what a settings screen promises.
- Step 03
Count the Product blocks
If a page carries two Product nodes, I find out which plugin or template prints each one. The incomplete one goes, or gets merged into the other, so the page ends up with one Product and one complete offer.
- Step 04
Give it a real price, or drop the Product type
A product that's for sale gets its real price in the product data. A page that has no price, like a quote-only item, isn't a merchant listing, so unless it has real reviews to mark up I remove the Product type there rather than invent a number.
- Step 05
Check variable products on their own
Variations are where offers go missing most often. I test one variable product with a single price and one with a price range, because WooCommerce prints the two differently.
- Step 06
Test on staging, then on the live site
Where there's a staging copy I test the change there first. Then I clear the cache and retest the same URLs live, since a cached page keeps serving the old markup.
- Step 07
Validate the fix in Search Console
Once every instance is fixed, I press Validate fix on the issue. Search Console's help says it rechecks only the URLs it had listed with that issue, not the whole site, and that validation typically takes up to about two weeks, sometimes much longer. So I keep an eye on the report instead of calling it done on day one.
Don't clear the error by pasting in a rating block with a made-up score. It'll pass a syntax test today, and it breaks Google's review guidelines the day it goes live.
Which merchant listings warnings can I leave alone?
Most of them, for now. In the merchant listings documentation, review and aggregateRating are recommended, not required, and so are shippingDetails, hasMerchantReturnPolicy, brand and gtin. An item missing a required property isn't eligible for rich results, while recommended ones only make the result richer, so a missing recommended property lands in the non-critical table.
That's why a store can see a long list of warnings, including the missing field review one, with nothing broken. I read each warning as a question: does the store have this fact? If it does, I mark it up. If not, the warning stays.
| Missing property | What it describes | What I do |
|---|---|---|
| priceValidUntil | The date the price stops applying | Core WooCommerce prints it, as the end of next year or the sale's end date, so a warning points at a second Product block. Google says a past date can stop the listing showing |
| sku | The store's own product code | Core WooCommerce prints the SKU, or the product ID when the field's empty, so I fill in the real SKU under Product data, Inventory |
| review | A customer review of the product | Leave it until real reviews are on the page |
| aggregateRating | The average score across those reviews | Same: leave it until real ratings exist |
| shippingDetails | Shipping cost and delivery time | Add it once the store has fixed shipping rules to describe |
| hasMerchantReturnPolicy | The store's return terms | Add it once there's a written return policy to match |
| gtin or brand | Product identifiers | Add the real ones if the store holds them, never made-up codes |
Why won't I add review markup without real reviews?
Because Google's rules say no, and they're specific. Its general structured data guidelines name fake reviews as misleading content you shouldn't mark up. The review snippet guidelines say marked-up reviews have to be readily available to users from the marked-up page, with no fake or undisclosed incentivised reviews, and not aggregated from other websites, so stars copied from a marketplace or a Google Business Profile don't qualify either. For a Review on a Product, the author has to be a Person or Organization with a valid name.
The cost isn't only the fake stars. Google says a structured data manual action means the page loses eligibility to appear as a rich result, though it doesn't change how the page ranks in web search. The honest price markup sits on the same page, so a made-up rating can take the product snippet down with it until the action's lifted.
A hard-coded rating in a theme, or a schema plugin's demo defaults, is the same mistake in quieter form, and I take it out.
When a store has real reviews, WooCommerce handles the honest version itself: with ratings on and at least one rating, core prints aggregateRating from that page's reviews. I check it matches what a shopper sees. The wider product page work, titles, categories and internal links, is part of the WooCommerce work I do on product and category pages.
If you'd like one person to trace your product markup to its source and fix it without inventing a single star, that's the structured data work I do, remotely, over Zoom, Meet or async notes. Here's how I scope a job.
Book a call on Upwork
