The problem in 60 words
Google is the one engine that already sees your customer reviews, because it executes your widget’s JavaScript. The engines behind ChatGPT and Claude do not. We measured 457 French Shopify product pages: among stores running a reviews app, one in four exposes no rating at all without JavaScript. The sample is French, the mechanism is not: it is how reviews apps ship by default.
What we measured
On 13 August 2026, we read one product page on each of 480 French Shopify stores (clients of agencies listed in the Shopify partner directory), over plain HTTP, with no JavaScript execution. This is what a crawler that does not render pages sees.
| Measure | Result |
|---|---|
| Stores queried | 480 |
| Product pages resolved and analysed | 457 |
| Load a reviews app on the product page | 249 / 457 (54.5%) |
| Of those 249: no review signal at all without JavaScript | 61 (24.5%) |
| Of those 249: rating readable in some form | 188 (75.5%) |
Of those 249: AggregateRating attached to the Product | 27 (10.8%) |
Method: one product page per store, resolved through /products.json then the product sitemap as a fallback. Requests over plain HTTP with the user-agent VerityScoreBot/1.0, no JavaScript execution. We searched for the rating and review count in four forms: JSON-LD aggregateRating, itemprop microdata, widget attributes (data-number-of-reviews, data-average-rating), and visible text such as “128 reviews” or “4.6 / 5”. Reviews apps were detected by their own markers: CDN domain and widget container present in the served DOM.
Limits, and they matter. A French sample of agency-supported stores, so probably better maintained than average.
One page per store, often a recent product that may legitimately have no reviews yet, in which case a missing rating is correct rather than broken. That is why the meaningful line is not “24.5% have no reviews” but “24.5% load a reviews app while no rating appears anywhere in the HTML”.
23 of the 480 stores dropped out because no product page could be resolved: /products.json disabled, no product sitemap, or a non-200 response. Those are typically stores that filter bots or serve a decoupled front end, so they are likely less readable than average. Excluding them makes our figures optimistic, not the reverse.
Finally, we queried each store with an identifiable user-agent rather than impersonating an AI crawler. A store that serves different HTML per user-agent would appear here under its “unknown bot” face. We will re-run this measurement on the same corpus in February 2027.
One measured example makes the mechanism concrete. A fragrance store in the sample serves data-number-of-reviews="13960" and a 4.65 rating in its HTML, yet publishes no aggregateRating in its JSON-LD. Nearly 14,000 reviews exist, are counted server-side, and are never declared in the format engines look for.
Why Google is not the issue
This runs against the mood of the moment. The French launch of AI Overviews put Google at the centre of every conversation, when Google is the engine that needs your help the least on this particular point.
Google documents three stages: crawling, rendering, indexing. Rendering runs through “a headless Chromium [that] renders the page and executes the JavaScript” (Google Search Central). Your reviews widget runs, the rating appears, Google sees it. Google does add caveats: a page can sit in the render queue “a few seconds, but it can take longer than that”, and the documentation still recommends server-side rendering, noting that not all bots can run JavaScript.
AI engine crawlers are exactly those bots. The joint Vercel and MERJ analysis found no evidence of JavaScript execution by GPTBot, ClaudeBot, PerplexityBot, Bytespider or meta-externalagent. They sometimes fetch JavaScript files, between 11% and 24% of requests depending on the crawler, but never execute them. They read the HTML as it leaves your server. The finding converges with more recent independent measurements (SearchOptimo, June 2026), and Anthropic explicitly recommends not relying on JavaScript for its crawler to read a page.
Hence the asymmetry nobody is looking at:
| Engine | Executes JavaScript | Sees a widget-only rating | Evidence |
|---|---|---|---|
| Google (Search, AI Overviews, AI Mode) | Yes, headless Chromium | Yes, after deferred rendering | Documented by Google |
| ChatGPT (GPTBot) | No | No | Measured |
| Claude (ClaudeBot) | No | No | Measured |
| Perplexity (PerplexityBot) | No | No | Measured |
| ChatGPT and Claude, other agents | Not expected | Not expected | Inferred, not measured |
Three honest caveats on this table, because it carries the whole thesis.
The Vercel/MERJ measurement covers GPTBot, ClaudeBot, PerplexityBot, Bytespider and meta-externalagent. It does not cover OAI-SearchBot, ChatGPT-User, Claude-SearchBot or Claude-User, some of which did not exist when the study ran. We group them by infrastructure consistency, not on direct evidence.
On the Google side, “Yes” deserves an asterisk: rendering goes through a queue. On a product whose rating moves, Google may read a stale value rather than no value.
Finally, OpenAI’s documentation describes its crawlers and their roles but says nothing about JavaScript. Anthropic, by contrast, explicitly recommends not relying on it. So the absence of execution is established by measurement on the OpenAI side, and by documentation on the Anthropic side.
What Google actually says about AI Overviews
Precision matters here, because the market says the opposite. Google states plainly that no particular structured data is needed to appear in AI Overviews or AI Mode: “You don’t need to create new machine readable files, AI text files, or markup to appear in these features” (Google Search Central).
So no, publishing an AggregateRating will not get you into an AI Overview. Selling that would be false.
What the markup actually does, and it is more than enough to justify the work:
- It gates the review snippet in Google results. To show stars, Google asks you to “mark up an aggregate evaluation of an item by many people with schema.org/AggregateRating”, with at least
ratingCountorreviewCount, and requires the review content to be visible to users on the page (Google Search Central). - It makes the rating readable by engines that do not execute JavaScript. That is the subject of this article.
Two concrete benefits, neither of which is “appearing in AI Overviews”. That distinction is what separates a verifiable recommendation from a sales pitch.
Five levels of machine readability
Behind an identical on-screen result (stars under the product title), our measurement surfaces five situations. The counts are mutually exclusive and sum to the 249 equipped stores.
| What the served HTML contains | Stores | Share |
|---|---|---|
1. AggregateRating attached to the Product | 27 | 10.8% |
2. Structured rating, but not attached to the Product | 10 | 4.0% |
| 3. Proprietary widget attribute only | 130 | 52.2% |
| 4. Visible text only | 21 | 8.4% |
| 5. No trace without JavaScript | 61 | 24.5% |
Level 1, the standard (10.8%). The rating is attached to the Product, in JSON-LD or microdata. Google accepts both, plus RDFa: it states that all three formats are “equally fine”, JSON-LD being merely the easiest to maintain (Google Search Central). This is the safest form for any engine parsing the page.
Level 2, the orphaned rating (4.0%). An aggregateRating exists, but on an entity other than the product, typically the Organization. These stores believe they are at level 1. They are not: a company-level rating does not make a product page eligible for stars.
Level 3, the proprietary attribute (52.2%). The rating lives in a widget container attribute, typically data-number-of-reviews="128" with Judge.me. The HTML does contain it, so a crawler that does not render JavaScript can read it if it knows where to look. But nothing is normalised, and Google does not use it for review snippets. This is the most common case, and it is half a signal.
Level 4, text only (8.4%). No markup and no attribute, but “128 reviews” and “4.6 / 5” are written into the server-rendered HTML. That is worse than markup for Google, and paradoxically rather good for a conversational engine, which reads text.
Level 5, silence (24.5%). The served HTML contains an empty widget container. No rating, no count, no text. JavaScript fills everything at load time. For Google, no problem. For ChatGPT and Claude, this store has no reviews at all.
An important caveat on level 1
JSON-LD is the best guarantee for engines that index, not a universal one. When browsing live, ChatGPT and Claude often read the visible text rather than the markup: our own per-engine extraction measurement gives 0% for Claude and 37.5% for ChatGPT when the price exists only in JSON-LD (see the detail).
So the safe rule is not “use JSON-LD” but both at once: the rating written in plain text in the server-rendered HTML, and an AggregateRating attached to the Product. Level 1 without visible text is still a bet.
The silence clusters on three apps
This is the most counter-intuitive result of the measurement. Fully silent stores are not spread at random: it depends on the app installed.
| App | Stores | At the standard (level 1) | Silent without JavaScript (level 5) |
|---|---|---|---|
| Judge.me | 154 | 13 (8.4%) | 0 (0%) |
| Trustpilot | 50 | 6 (12.0%) | 26 (52.0%) |
| Loox | 31 | 5 (16.1%) | 13 (41.9%) |
| Yotpo | 24 | 5 (20.8%) | 14 (58.3%) |
| Other vendors, grouped | 33 | — | — |
Judge.me is never silent. Across 154 stores, none is at level 5: the app always writes its counter into an attribute of the served HTML. That is half a signal rather than the standard, but a crawler reading raw HTML always finds something. Yotpo, Trustpilot and Loox, by contrast, leave between 42% and 58% of their stores completely silent.
Two caveats. Vendors under 20 observations are grouped: at those counts a “0%” reads as noise, not as a ranking. And Trustpilot is a special case: it is a business review platform, not a product review app. A correctly implemented Trustpilot score rates the Organization, so it is not supposed to produce a product-level AggregateRating. Its row measures a reality, not a fault.
The check, in three minutes
Step 1. Open the page source, not the inspector. This is the one real trap in the procedure. Ctrl+U (Cmd+Option+U on Mac) shows the HTML as the server sent it. The element inspector shows the DOM after JavaScript has run: your reviews always appear there, and you wrongly conclude everything is fine.
Step 2. Search for aggregateRating. With Ctrl+F in the source. Present with a ratingValue and attached to a Product block: you are at level 1, nothing to do. Present but attached to Organization: you are at level 2, the rating does not count for the page. Absent: go to step 3.
Step 3. Search for your review count as a number. If the page shows “128 reviews”, search for 128. Found inside an attribute like data-number-of-reviews: level 3. Found only in the text: level 4. Not found at all while the page displays it: level 5, and that is the priority fix.
For the fix, order matters, and the obvious shortcut does not work:
- Do not expect everything from your app’s “rich snippets” toggle. It is the natural reflex and it is often inert: with Judge.me, Loox and Yotpo, the markup that option produces is itself injected in JavaScript, so it stays invisible to the very engines discussed here. Only Okendo publicly documents server-side injection, and Stamped offers it as a manual theme snippet. Turn the option on, then check the page source: if
aggregateRatingdoes not appear, the toggle changed nothing for you. - The reliable fix goes through the theme. If your theme already publishes a
ProductJSON-LD (most recent Shopify themes do), add theaggregateRatingfield to it, fed by your review app’s metafields. That is the only route that guarantees server-side rendering. - Serve the rating as text too. Markup serves Google; server-rendered text serves conversational engines. Together they cover every case.
- Never mark up a rating the page does not display. Google requires review content to be visible to users. An
AggregateRatingwithout visible reviews is a policy violation, not an optimisation.
On multiple blocks: several Product blocks on one page are not a problem in themselves, Google reconciles them by @id. What causes problems is two blocks declaring different values. The full app-by-app fix procedure lives in our AggregateRating reference.
Then verify with Google’s Rich Results Test, and reload your product page with JavaScript disabled in the browser. What you read then is a good approximation of what a non-rendering crawler receives. The caveat: some servers vary their response by user-agent, so what GPTBot actually sees may differ. To remove the doubt, replay the request with its user-agent:
curl -A "GPTBot" -sL "https://your-store.com/products/your-product" | grep -c aggregateRating
What the measurement says about the rest of the page
Reviews are the widest gap, but the same read gives the state of every other signal. Across the 457 pages, in raw HTML:
| Signal | Present |
|---|---|
JSON-LD Product | 86.9% |
| Price | 67.4% |
| Availability | 66.7% |
| Brand | 85.6% |
| SKU | 35.7% |
| GTIN | 10.9% |
| Structured shipping | 4.6% |
| Structured return policy | 4.6% |
AggregateRating in JSON-LD attached to the Product | 6.1% |
Two points deserve attention. Price is missing in 32.6% of cases, but the cause differs from reviews: 60 pages have no Product JSON-LD at all, and 89 publish a Product without a price. The price almost always stays readable in the page text, because Shopify themes render it server-side. That is a loss of precision, not a disappearance. And 4.4% of pages carry at least one JSON-LD block the parser rejects: an invalid block is worth an absent one.
Structured shipping and returns, both under 5%, remain the blind spot of the French market. Yet they are two purchase-decision criteria that conversational engines quote readily when they find them.
What to take away
22 July shifted attention to Google. That is understandable, and it is not where your machine readability is decided, because Google already executes your widgets’ JavaScript. The gap sits elsewhere: for a quarter of stores running a reviews app, engines that read raw HTML see no rating at all. Those engines are where assisted buying conversations happen today.
The fix needs no redesign and no budget, but it does need to target the right place: a field added to the theme’s JSON-LD, rather than an app toggle that also produces JavaScript. What is usually missing is simply having looked at the right HTML.
One last caution on reading these numbers. They describe a population, not a product. At the scale of a single store the outcome depends on the integration mode chosen, not on the app’s name: our three store-by-store tests from 23 July produced three different results for identical apps.
Kamil Kaderbay, founder of Verity Score. Measured 13 August 2026; method and limits detailed above. Next measurement on the same corpus: February 2027.
Related articles
- AggregateRating: the rating AI engines actually read
- The schema.org = ChatGPT citations myth
- Optimising your Shopify store for GEO
- Blocked AI crawlers: the verification procedure
Want to know what engines read from your pages? The Verity Score Shopify app reads your product pages without executing JavaScript, like the ChatGPT and Claude crawlers, and lists the missing signals. Install Verity Score from the Shopify App Store →