August 26, 2026

Shopify schema markup: what 58 live stores actually serve

Shopify schema markup: what 58 live stores actually serve

Published 26 August 2026 by Adot Technologies Inc, the team behind Arvio. Every figure below comes from read-only GET requests to public product pages, made on 26 August 2026. We sell a Shopify app, so we have a side in this; the frame and the exclusions are in the method section at the end.

Key takeaways

  • Product and Offer markup is not the problem. 159 of 167 product pages we parsed (95%) carry Product JSON-LD, and every one of those carries offers. Shopify's own themes emit it; nobody has to switch it on.
  • Not one Product node in the sample carried aggregateRating. Nobody forgot to fill it in: neither of the two things that generate this markup emits the field at all. One store attaches a rating to its business block instead, which is 3 pages out of 167.
  • The fields that would let a machine compare your product with another shop's are the thin ones: a barcode on 14% of pages, shipping details on 5%, return policy on 7%. Whether any assistant actually reads them, we have no way to see.
  • BreadcrumbList, which tells a crawler where the product sits in your catalogue, is on 31%.
  • We read a public sample frame anyone can rebuild — stores whose owners posted their own URL on the Shopify Community asking for feedback.

The quick version

One thing first, because it changes how you should read everything below: these are 58 stores whose owners posted on a Shopify Community board asking strangers for feedback. They skew new and small — median catalogue 46 products. If you run several thousand SKUs, treat this as a lead, not a finding.

If you are searching for "Shopify schema markup" because you think your store is missing structured data, it very likely isn't. Open any product page, view source, search for application/ld+json. On 95% of the pages we read, Product and Offer are already there.

What is missing is what an AI shopping assistant reads after it has found your product: the rating, the return window, the delivery estimate, the barcode that tells it your product is the same one another shop sells. Filling those in is a different job from "adding schema markup", and for some of them your theme is not where you would change it anyway.

What 167 product pages carry

Every figure below is a share of the 167 product pages that returned HTTP 200. Nested types count too, not just the top-level @type.

Type Pages Share
Offer 159 95%
Product 159 95%
Brand (as its own node) 153 92%
Organization 152 91%
ProductGroup 52 31%
BreadcrumbList 51 31%
ListItem 51 31%
WebSite 14 8%
MerchantReturnPolicy 12 7%
MonetaryAmount 12 7%
OfferShippingDetails 9 5%
QuantitativeValue 9 5%
ShippingDeliveryTime 9 5%

Brand appears as its own node on 92% of pages, but the Product node's own brand field is filled on only 69% — 49 pages carry a Brand node the Product never points at. That gap has a cause, and it is what the next section is about.

At store level: 54 of 58 stores (93%) had Product JSON-LD on every product page we sampled, and 4 had it on none. Nothing in between — and for 56 of the 58 that verdict rests on two or three pages, not one. It's a theme-level switch, not something anyone is doing product by product.

Where this markup comes from

Two different things generate that JSON-LD block, and which one your store runs decides what you can change.

Shopify's own default theme, Dawn, hand-wrote the block up to v14: a literal {"@type": "Product", ...} template carrying name, url, image, description, sku, brand and an offers array. In the current version that template is gone, replaced by a single line, {{ product | structured_data }}, which hands the job to a platform filter.

Shopify's documentation for that filter says: "Product objects are output as a schema.org Product if they have no variants, and a Product Group if they have one or more variants."

That sentence accounts for the ProductGroup on 31% of the pages we read. Nobody chose that; it is what having variants does. Our own data agrees: 12 of the 58 stores had ProductGroup on some product pages and not others, which is what you see if the split is per product rather than per store.

It also accounts for the Brand gap above. On pages that emit a ProductGroup, brand and description hang off the group rather than off the individual Product — 6% of ProductGroup pages have brand on the Product node, against 100% of the pages without one. Nothing was missing; our per-node reading was looking at the wrong node. So the table below counts both.

Neither generator emits a rating. The filter's documented output carries @id, @type, brand, category, description, image, name, offers and url. The hand-written Dawn block carries much the same set plus sku and the barcode. The strings aggregateRating, shippingDetails and hasMerchantReturnPolicy appear nowhere in either — we checked Dawn v13, v14 and current: zero occurrences in all three. So a 0% rating is not 58 merchants forgetting a field. Nothing on these pages was ever going to write one.

We only read Dawn, which is Shopify's default and the base many other themes are built on. We did not audit the theme store, and a theme can write whatever it likes: the 14% of pages carrying a barcode are evidence that some do.

Inside the Product node

Two columns, because which node a field sits on is a theme implementation detail rather than a merchant decision. The first is the Product node alone. The second counts the field on any node in any block, which is the question a machine reading the page actually has: is this value here anywhere?

Field On the Product node (of 159) Anywhere on the page (of 167)
offers 159 · 100% 159 · 95%
image 155 · 97% 155 · 93%
brand 110 · 69% 159 · 95%
description 107 · 67% 153 · 92%
sku 87 · 55% 88 · 53%
barcode (gtin/gtin8/gtin12/gtin13/gtin14) 15 · 9% 23 · 14%
mpn 10 · 6% 10 · 6%
aggregateRating 0 · 0% 3 · 2%
review 0 · 0% 0 · 0%

offers at 100% means price and availability are machine-readable on every page that carries Product markup. That's the half Shopify does for you.

The gap between the two brand columns — 69% against 95% — is the ProductGroup split, not 26% of stores missing a vendor name. The gap in the barcode row is our own earlier mistake, described in the method section.

No rating on any product node

An AI shopping assistant answering "which of these is worth buying" has to rank. Price it can read from offers on every store. Rating it can read from none of them.

Not one of the 159 Product nodes carried aggregateRating. One store — 3 pages of the 167 — carries an AggregateRating, but it hangs off a Store block describing the business, not off any product. An assistant comparing two dresses gets nothing from it.

Most of this is the generator: it does not emit the field. Shopify has no native product reviews either, so a rating has to come from a review app, and the app has to inject its own JSON-LD for the rating to appear at all.

There is a second explanation we did not test: our frame skews toward new and small stores, and a store with three orders has no ratings to mark up. That would depress the number on its own. Both can be true at once. Either way, on these 167 pages a machine looking for a product rating finds one on none of them.

The barcode, on 14% of pages

gtin is the barcode — UPC, EAN, ISBN. It's how a machine decides that your "Blue Merino Crew, Medium" and another shop's listing are the same physical product.

23 of the 167 pages carry one, spread across 13 of the 58 stores. It is also the one field here that tracks something a merchant typed: in the hand-written Dawn block, the barcode is emitted only when the variant's Barcode field is filled in, and it goes out as gtin12 or gtin13 depending on how many digits you typed.

Without it, an assistant has to match on title and image, which falls apart across shops. With it, your product joins a comparison set — and whether you want to be in that set depends on whether you win on price or delivery. If you lose that comparison, the barcode helps the buyer and not you.

If you make your own products, there may be no barcode to add and none of this applies.

Shipping and returns: 5% and 7%

OfferShippingDetails and MerchantReturnPolicy are what an assistant would read to answer "will it arrive by Friday" and "can I send it back". Nine and twelve pages respectively, and where we could trace them they hang off the Offer node rather than the Product.

Neither appears anywhere in the Dawn versions we read, so wherever they do show up, an app or a custom theme put them there rather than the platform. Why so few stores have either is a separate question we did not look into.

What you can actually change

See what you already have. Open a product page, view source (Ctrl+U / Cmd+Option+U), search for application/ld+json and paste the block into any JSON viewer. Look for "@type": "Product" or "@type": "ProductGroup", then check whether a barcode field, aggregateRating, shippingDetails and hasMerchantReturnPolicy are populated anywhere in it. Google's Rich Results Test reads the rendered page and shows the same thing with less squinting.

Then work out which generator you are on, because that decides whether the block is yours to edit. In your admin: Online Store → Themes → ⋯ → Edit code → sections/main-product.liquid, and search for ld+json. If the next line is {{ product | structured_data }}, the field set belongs to Shopify and editing around it won't add fields — you would be adding a second JSON-LD block, or letting an app add one. If instead you find a hand-written {"@context": "http://schema.org/", ...} template, that is your theme's code and its fields are editable.

The barcode is the one field that is merchant data, not theme code. On the hand-written block it is emitted from the variant's Barcode field, so filling that field in is the whole change. It is also the one worth thinking about before you do it, for the reason above.

The rating comes from your review app, or from nowhere. If reviews matter in your category, the question to ask your review app's support is whether it writes aggregateRating into the page's structured data, and whether that setting is on.

None of this tells you whether an AI assistant uses the field. Nobody outside those companies knows that.

What this is not

  • This does not show that adding these fields lifts traffic or sales. We measured what stores serve, not what any search engine or assistant does with it. We have no data linking these fields to revenue.
  • It is not representative of Shopify as a whole — see Method for how the frame was built and where it is skewed.
  • And it says nothing about any particular store. We name none of them.

Method

Sample. 58 live Shopify storefronts, 167 product pages, read on 26 August 2026. The owners had posted their own store URL publicly on the Shopify Community's Store Feedback board, asking for feedback — a public list anyone can rebuild. Everything here came from endpoints any browser can fetch.

How the frame was built. 720 topics read from that board (posts dated 2025-01-15 to 2026-08-26) → 474 candidate hostnames extracted from first posts only → 230 verified as live Shopify storefronts → 60 drawn at random (random.Random(20260826).sample, reproducible) → 58 had a readable catalogue. Of the two that dropped out, one returned nothing on /products.json and one had an empty catalogue.

Per store. Up to 3 product pages, sampled at even intervals across the catalogue rather than taking the first three — the newest products are not representative of the rest.

Parsing. Full HTML, no truncation. Each <script type="application/ld+json"> block is JSON-parsed and walked recursively, so types nested inside @graph or inside an offers node are counted. 9 of 404 blocks failed to parse. They stay in the denominator — a page whose block we could not read still counts as a page — so every share in this article is a floor. We flag them rather than drop them because dropping them would quietly inflate every percentage.

This is not a random sample of Shopify stores. It is a random draw from a public convenience frame. The frame's own biases, which we did not correct for:

  • Owners who ask strangers for feedback skew new, small and struggling. The median catalogue among the 58 stores is 46 products (the two middle stores hold 44 and 47), and that is a floor rather than a measurement: we read at most 250 products per store, and 16 of the 58 hit that ceiling. If you run a catalogue of several thousand, this sample is not describing your store.
  • Password-protected and pre-launch stores fail verification and are excluded. Between the addresses posted to the board and the verified list, half dropped out — survivorship is the biggest bias in here, and it pushes the sample toward stores that are still trading.

Two measurement errors of ours, both corrected here. We are writing them out because both times the wrong number was ours, and both times it was wrong the same way: it measured our own matching rule rather than the stores.

  1. Truncation. An earlier probe scanned only the first 200,000 characters of each page and reported 33% Product JSON-LD coverage. 158 of the 167 pages we read are longer than that — median 446,000 characters, largest 2,059,072 — and Shopify themes commonly place JSON-LD late in the document. Full-HTML re-read: 95%.
  2. The barcode is not one key. schema.org spells it gtin, gtin8, gtin12, gtin13 and gtin14, and the hand-written Dawn block emits gtin12/gtin13 on the Offer node, not on the Product. A first pass that looked for a literal gtin on Product nodes returned 8%. Reading all five spellings on any node returns 14%.

Every figure in this article comes from the corrected re-read.

What we still don't know

  • Whether any AI assistant weights aggregateRating or gtin when it picks products to cite. We can see what is served; we cannot see what is consumed.
  • Whether the 4 stores with no Product markup are running a custom theme, an older theme, or have it stripped by an app.
  • How many distinct themes this is. Markup is a theme-level property, so 58 stores may be far fewer than 58 independent observations — a popular theme could account for a large share of the sample on its own. We did not record which theme each store runs, so we cannot say.
  • Whether bigger stores — the ones that never need to ask strangers for feedback — look different. We would expect them to, and we have not measured it.

FAQ

Does Shopify add schema markup automatically?

On this sample, yes for the base types — 95% of product pages carried Product and Offer JSON-LD. In Shopify's current default theme it comes from a platform filter, {{ product | structured_data }}, so the base field set is not something the merchant switched on or can edit in the theme.

What schema markup does a Shopify product page need?

Product with offers is the base, and it is almost always already there. The thin ones across this sample were the rating (0% of product nodes), the barcode (14% of pages), shipping details (5%) and return policy (7%).

Why does my Shopify store have no aggregateRating in its structured data?

Because nothing on the page is generating it. Shopify's structured_data filter does not emit the field, and neither did the hand-written block in older Dawn. Shopify also has no native product reviews, so the rating has to come from a review app that injects its own JSON-LD. Check whether yours has a structured-data or rich-snippets setting.

Is schema markup worth it for AI search?

Nobody outside the AI vendors can answer that with evidence. What we can say is that price is machine-readable on every page that carries Product markup and a product rating on none of them, so if an assistant does use ratings, these stores have nothing to show it.

Does adding GTIN help my Shopify store?

It makes your product matchable against the same product in other shops, which helps a buyer comparing and does not necessarily help you. On themes that hand-write their JSON-LD it comes straight from the variant's Barcode field. If you make your own products, they may have no barcode at all.


Related: Every Shopify store we read is already serving an agent API — the discovery layer above this one. Different sample, and it reads dedicated endpoints rather than parsing page HTML, so the truncation bug described in the method section here does not touch its numbers.

The fields above — barcode, description, vendor — are product data before they are markup, and they go stale the way any catalogue field does. That is the part we work on: Arvio: AI Store Operator from Adot Technologies Inc lets you ask what is missing across your catalogue, review every edit before it goes live, and undo anything. It does not write JSON-LD, and nothing in this article was measured with it.

Arvio: AI Store Operator

Other AI tools give you a to-do list.
Arvio's AI store agent does the work.

Arvio scans your Shopify store daily, finds what needs fixing — SEO, prices, stock, product content — and drafts the fix. You approve it, Arvio ships it. Every edit can be undone.

Install on the Shopify App Store
4.9★ rated on Shopify App StoreFree trial availableEvery edit reversible