Skip to content
AgentReady
By Dylan HuntNovember 12th, 2026Structured dataStructured dataProduct schemaJson ld

Three Things Write Structured Data to Your Product Page. Here Is How to See Who Wins

You run a product page through a validator and it reports two Product items, or two ratings with different numbers. Nothing is broken on the page. Two different pieces of software each wrote their own description of the product, and neither knows about the other.

On a Shopify store, three things commonly write structured data to a product page: the theme, a review app, and a schema app such as AgentReady. This post shows how to list what is on your page, tell which block came from where, decide whether the duplicate matters, and switch one off.

Where the blocks come from

The theme. Most current Shopify themes print product markup with the structured_data Liquid filter. Shopify's docs say the filter "converts an object into a schema.org structured data format", and that a product is output as a ProductGroup "if they have one or more variants". Many themes also print an Organization block from the layout.

A review app. Review apps add AggregateRating and sometimes individual Review items so stars can show in search results. Some write the block into the page's HTML. Others add it with JavaScript after the page loads. Google reads both: its docs say it "can read JSON-LD data when it is dynamically injected into the page's contents".

A schema app. Apps like AgentReady write a fuller block: the product and its variants, shipping and returns, the store itself. If the theme's block is still on, you now have two descriptions of the same product.

What this looks like on real pages

We fetched two product pages on 12 November 2026.

A stock theme. The Dawn demo store's product page for the Bo Ivy bag carries two JSON-LD blocks, both from the theme. The first is the store's Organization. The second is the product, written by the structured_data filter:

{"@context":"http:\/\/schema.org\/","@id":"\/products\/bo-ivy-black#product","@type":"ProductGroup","brand":{"@type":"Brand","name":"Mlouye"}, ...

Note the @id ending in #product. That pattern is a good sign a block came from Shopify's filter. Install a review app and a schema app on this store without changing anything else, and this page would carry up to four blocks describing one bag.

A store with one owner. William Painter, one of our lab stores, runs AgentReady. Its product page for The Hook carries exactly one JSON-LD block, in both the page source and the rendered page:

<script type="application/ld+json" data-agentready="product-jsonld" data-shop-graph="ref">

Inside it is one graph: OnlineStore, WebSite, WebPage, BreadcrumbList, OfferShippingDetails, and a ProductGroup with five color variants. The theme's own product block is switched off. The page's code still mentions three review apps by name, but none of them writes structured data, so there is no second rating to disagree with the first. That is the end state you want: one block per fact, and you know who wrote it.

How to list the blocks on your page

Use all three methods. They catch different things.

  1. View the page source. Open a product page, choose View Page Source in your browser, and search for application/ld+json. Each match is one block. This shows what the server sent, but not blocks added later by JavaScript.

  2. Ask the rendered page. Open the browser console on the same product page and paste:

    [...document.querySelectorAll('script[type="application/ld+json"]')].map((s) => {
      const d = JSON.parse(s.textContent);
      const items = Array.isArray(d) ? d : d["@graph"] || [d];
      return items.map((x) => x["@type"]).join(", ") + " | " + s.outerHTML.slice(0, 120);
    });
    

    This lists every block after scripts have run, with the start of each script tag so you can see its attributes.

  3. Run Google's Rich Results Test on the live URL. Google calls it "an easy and useful tool for validating your structured data". It shows what Google read, including injected blocks, and flags errors.

How to tell who wrote each block

  • Attributes on the script tag. AgentReady marks its blocks with data-agentready. Some apps use their own attribute or an id. The theme's blocks usually have none.
  • The @id. Shopify's filter produces ids like /products/handle#product. Apps tend to use full URLs or their own suffixes.
  • Where the block sits. A block printed in the middle of the product section is usually the theme's. One near an app's script files is usually that app's.
  • Switch and recheck. Turn off one app's embed in the theme editor, reload, and list the blocks again. The block that disappears was that app's.

Which duplicate matters

Not every duplicate is a problem. Two copies that say the same thing are untidy, not harmful. The ones worth fixing disagree on something a shopper or a machine relies on:

  • Price, availability or currency.
  • Product and variant identity. Google's variant docs say that when the group and its variants both carry an identifier, "they must match", and that "each product group must have a unique ID". Two groups for one product break that.
  • Ratings. Two AggregateRating values for one product cannot both be right. Google's review guidelines also say that "if you use AggregateRating, users should be able to see that aggregate rating on the page", so the number in the markup should be the one your widget shows.
  • Shipping and returns terms that differ between blocks.

If two review apps are involved, our post on two review apps writing review schema walks through that case.

How to switch one off

Pick one owner for each fact, then turn the others off.

  • Theme product block. Some themes have a setting for it. In Dawn and themes built on it, the call is {{ product | structured_data }} in the main product section. Duplicate the theme first, remove the call in the copy, preview it, then publish. Keep the theme block if nothing else describes the product.
  • Review app. Look in the app's settings for a rich snippets or SEO markup option. If it injects markup through an app embed, the toggle may be under Online Store > Themes > Customize > App embeds. Keep the review widget; only the duplicate markup goes.
  • Schema app. If you would rather keep the theme's block, switch off the app's product markup instead.

Then list the blocks again on the live page. A conflict is only fixed when the live page shows one block per fact.

What AgentReady does about it

AgentReady reads every structured data block on your live pages, not only its own, and tells you where each one came from. It marks the conflicts that matter, the ones that disagree on price, availability, variants, reviews or policies, as urgent. It never edits your theme files. Most conflicts end with one switch you can flip yourself. If you would rather we did it, cleanup is a one-time $100, or included with an annual plan. The structured data page explains how detection works.

To see how many blocks your product pages carry today, run the free audit. For the markup itself, start with the Shopify product schema guide.

See where your store stands

See what AI shopping assistants can read on your store.

Run the free AI-Readiness Checker to see, in under a minute, how ChatGPT, Perplexity, and Google read your store today and exactly what is holding it back. Then AgentReady prepares the fixes, starting with Schema.org structured data, and publishes them after you approve. Install free. Paid plans start at $9/mo.

Comments

Every comment here comes from a verified email. Write yours, confirm from your inbox, and it's live.

Loading comments…

Leave a comment

ShareXLinkedInFacebook

Written by Dylan Hunt, Founder, AgentReady. AgentReady measures what AI says about Shopify stores and helps teams improve the answer. See plans.