Why this audit, why now
Google's product rich results — the price, rating, and availability snippets that show under a search result — depend on one thing: a valid `Product` JSON-LD block on the product page. Get it right and your listing gets a star rating, a price, sometimes a "Sale" badge. Get it wrong and the result looks like everyone else's plain blue link.
I expected most sites to have it right by 2026. After all, every Shopify, WooCommerce, and Magento install ships Product schema out of the box. But I kept seeing my own clients' competitors with broken schema — and over the past three weeks I wanted real numbers, not anecdotes.
Below is what I found auditing 50 e-commerce sites: a mix of indie Shopify stores, mid-market WooCommerce, a few big-name brands, and three custom-built sites. The audit ran in early May 2026 using two Krawly tools side by side: the Structured Data Validator (which parses every JSON-LD block on a page) and Google's own Rich Results Test for cross-verification.
A note on bias: I'm the founder of Krawly. The tools I used are mine. I also used Google's official Rich Results Test on every "validated" result to make sure Krawly was not over-reporting passes. The two agreed in 48 of 50 cases. The two disagreements are flagged below where they matter.
The audit setup
50 product URLs. One product page per site (the highest-traffic SKU per Search Console where the owner gave me access, otherwise the homepage's featured product). Same checks across all 50:
1. Does any `Product` JSON-LD exist at all?
2. Does it validate against the Schema.org `Product` type's required fields?
3. Are the listed prices / availability / ratings accurate against the visible page?
4. Are images referenced by absolute URL and actually reachable?
5. Are reviews present and using the correct `AggregateRating` shape?
6. Is the SKU / GTIN / MPN block present?

I logged every issue per site, then aggregated. What follows is the pattern data, not 50 individual case studies.
The headline numbers
| Check | Sites passing | Sites failing |
|---|---|---|
| Has any Product JSON-LD | 46 | 4 |
| Required fields present | 38 | 12 |
| Prices match the visible price | 41 | 9 |
| Image URLs absolute and reachable | 34 | 16 |
| AggregateRating present and valid | 22 | 28 |
| SKU / GTIN / MPN block present | 19 | 31 |
Read it as: ~24% of sites had outright invalid product schema. About 60% had broken or missing review data. About 62% were missing identifiers Google explicitly says it wants.
The four sites with zero Product JSON-LD were not what I expected. Two were custom-built marketing-led sites that had stripped the schema during a redesign. Two were old WooCommerce installs where the Yoast SEO plugin had been disabled and never replaced. None of the four were Shopify.
The eight mistakes I saw repeatedly
1. `offers.price` is a number instead of a string
By far the most common bug. Schema.org's `Offer.price` field expects a string (`"39.99"`) — not a number (`39.99`). Validators are lenient and will often accept the numeric form, but Google's Rich Results Test increasingly flags it as a warning. 17 of 50 sites had this.
Why? Most CMS auto-generate the JSON-LD by interpolating a price variable directly. The developer wrote `"price": ###PRICE###` and the templating engine output a bare number.
The fix: wrap the price in quotes at the template level so the output is `"price": "39.99"` rather than `"price": 39.99`.
2. `priceCurrency` is missing or set to a label like "USD$"
Eleven sites had a price but no currency. Six more had a currency set to something Google does not accept ("USD$", "TL", "$"). The Schema.org spec is strict: it must be a valid ISO 4217 currency code ("USD", "EUR", "GBP", "TRY", etc.). Three letters, no symbols.
If you trade in Turkish lira, write "TRY", not "TL". If you trade in pounds, "GBP", not "£" or "GBP£".
3. Stale availability — the page says "In stock" but the schema says "OutOfStock"
This one bit nine sites. The product was visibly listed and purchasable on the page; the JSON-LD said `"availability": "https://schema.org/OutOfStock"`. The cause was always the same: the inventory check runs at request time and updates the page, but the JSON-LD was server-rendered at deploy time and cached.
Google reads the schema, not the visible HTML. If your schema says OOS, your product gets filtered from price-comparison and shopping carousels even when it is in stock.
The fix: include the inventory state in the same render pass as the schema. If you use Next.js or a Jamstack setup with ISR/SSG, make sure the page rebuilds when stock changes, not just when the description changes.
4. `image` is a relative URL or a CDN path missing the protocol
Sixteen sites had image references that worked when the schema was rendered (inside the page DOM the relative path resolves) but failed when Google's parser pulled them out of context. Sample bad values seen:
The fix: emit absolute HTTPS URLs. `https://example.com/uploads/img/sku-12345.jpg`. Schema.org `image` accepts arrays — pass multiple high-res variants.
5. `AggregateRating` exists but is empty, or uses `reviewCount: 0`
Twenty-eight sites had no review schema. Of the 22 that did, four had `"ratingValue": 0` or `"reviewCount": 0` — which validates as well-formed JSON-LD but tells Google literally nothing to display. The rich snippet is suppressed.
If you have zero reviews, omit the `AggregateRating` block entirely. Empty data is worse than no data: it tells Google "we tried and there's nothing to show".
6. `AggregateRating` references reviews that are not on the page
Six sites had AggregateRating values that did not match the visible review count or average on the page itself. Google's documentation is explicit: review schema must reference reviews that are actually visible to the user on the same page. If your sidebar shows "4.7 stars (203 reviews)" but the schema says "4.9 stars (89 reviews)", you are at risk of a manual action for "misleading structured data".
7. Missing identifier — `sku`, `gtin`, `mpn`, or `productID`
Thirty-one sites had a Product block with no identifier of any kind. Google's structured data documentation explicitly recommends including a product identifier so its product graph can dedupe and match across stores.
If you sell branded products (someone else's), use GTIN-13 or MPN. If you sell your own products, use your internal SKU in `sku`. Any identifier is better than none.
8. Schema is inside a JS-rendered `<script>` that Google may or may not execute
Three sites had their Product schema injected by client-side JavaScript after page load. Googlebot does render JavaScript and usually picks it up — but rendering is queued, not immediate. For high-priority pages you want the schema in the initial HTML response, not added on hydration.
You can verify which path your schema takes with the Rich Results Test's "View tested page → HTML" tab. If the schema is in the rendered HTML but not in the initial fetched HTML, you are at the mercy of Google's render queue.
Fixing all eight at once with a generator
For owners who do not want to hand-write JSON-LD, Krawly's Product Schema Generator outputs schema that avoids all eight of the above mistakes:

Paste the result into the `
` of your product page inside a `