E-Commerce13 min read

I Audited 50 E-Commerce Sites for Schema.org Product Markup — Most Are Wrong

Real numbers from a 50-store schema audit: which fields are missing, which validate but lie, and the eight mistakes I saw repeatedly enough to call a pattern. With copy-paste fixes.

Enis Getmez avatarBy Enis GetmezFounder & Lead Engineer

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?

Krawly Structured Data Validator — parses every JSON-LD block on a page
Krawly Structured Data Validator — parses every JSON-LD block on a page

I logged every issue per site, then aggregated. What follows is the pattern data, not 50 individual case studies.

The headline numbers

CheckSites passingSites failing
Has any Product JSON-LD464
Required fields present3812
Prices match the visible price419
Image URLs absolute and reachable3416
AggregateRating present and valid2228
SKU / GTIN / MPN block present1931

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:

  • `/uploads/img/sku-12345.jpg` (relative, no host)
  • `//cdn.example.com/img.jpg` (protocol-relative, breaks in some parsers)
  • `http://example.com/img.jpg` for a site that is now HTTPS-only (Google follows the redirect but the original URL is logged as broken)
  • 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:

    Krawly Product Schema Generator — paste product details, copy valid JSON-LD
    Krawly Product Schema Generator — paste product details, copy valid JSON-LD
  • `price` is always emitted as a quoted string
  • `priceCurrency` is validated against ISO 4217 before output
  • `availability` is enumerated against Schema.org's vocabulary (InStock, OutOfStock, PreOrder, etc.)
  • `image` is required and forced to absolute HTTPS
  • `AggregateRating` is optional — omitted if you don't provide review data
  • `sku` is encouraged and labelled
  • Paste the result into the `` of your product page inside a `