Home · The 15 signals · Product JSON-LD
Shopify Product JSON-LD
Product JSON-LD is the single largest signal in the CatalogScan score: 30 of 170 possible points, more than any other test. Every AI shopping agent — ChatGPT Shopping, Perplexity Shopping, Google AI Mode, Shopify's own Global Catalog — parses your product detail pages' JSON-LD first and only falls back to scraping the page body if the structured block is missing or malformed. This page explains the exact shape agents need, how to test what your theme is actually emitting, and how to fix it in product.liquid.
<script type="application/ld+json"> block on every product detail page (PDP) with "@type": "Product" describing price, availability, brand, GTIN, SKU, reviews, and description in machine-readable form. Either a single Product node (one PDP = one product) or a ProductGroup with hasVariant children (one PDP = a parent product with multiple variants). Anything else — Organization-only, WebPage-only, raw microdata, no JSON-LD at all — scores zero.
What it is
JSON-LD ("JSON for Linked Data") is the structured-data format Google, OpenAI, Anthropic, Perplexity, and every other major AI agent agree on for reading product pages without needing to parse raw HTML. On a Shopify product detail page, it lives in a <script type="application/ld+json"> block near the bottom of the page, and the load-bearing field is the top-level @type: only Product and ProductGroup count for AI-shopping discovery. Organization, WebSite, BreadcrumbList, WebPage can be present alongside but do not score this signal.
The minimum viable shape an AI agent expects on a single-variant PDP:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Wool Runner — Natural Black",
"image": ["https://yourstore.com/cdn/shop/.../runner.jpg"],
"description": "Merino-wool runner with cushioned EVA midsole...",
"sku": "AB-WR-NB-09",
"gtin13": "0012345678905",
"brand": { "@type": "Brand", "name": "Allbirds" },
"offers": {
"@type": "Offer",
"url": "https://yourstore.com/products/wool-runner",
"priceCurrency": "USD",
"price": "98.00",
"availability": "https://schema.org/InStock"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.7",
"reviewCount": "3284"
}
}
</script>
For a multi-variant PDP (one parent with size/color options) a single Product node is the wrong shape — see our long-form on ProductGroup JSON-LD for the correct ProductGroup + hasVariant shape.
Why AI shopping agents care
Three reasons this signal weighs more than any other:
- It's the source of truth for downstream signals. Brand entity, AggregateRating, Offers availability, canonical URL hygiene, GTIN, SKU — every one of those is read out of the same Product JSON-LD block. If the block is missing, those signals cannot score either; you lose the floor and every dependent ranking-spread signal at once.
- Agents read this first, scrape body second. Crawling the rendered HTML body, walking the DOM, and inferring price + availability + reviews is orders of magnitude slower and orders of magnitude noisier than reading one JSON object. Stores that emit clean JSON-LD get crawled at higher frequency and lower error rate.
- It's the canonical citation surface. When ChatGPT Shopping cites your product in a response, the
name,image, andoffers.pricefields are what the user sees in the rich card. Missing JSON-LD = degraded card = lower click-through even when you do show up.
How to test it on your store
The fastest hand-test, on any one PDP, is two steps:
- View the page source (Cmd-U / Ctrl-U), search for
application/ld+json. - Inside the matching
<script>blocks, find the one whose top-level@typeequals"Product"or"ProductGroup". If neither exists, you score zero.
To validate the JSON parses correctly and matches the schema vocabulary AI agents expect, paste your full PDP URL into Google's Rich Results Test — any red X under "Detected items" means a malformed block, which silently disqualifies you even if the data looks right at a glance.
The fastest way to test all your PDPs at once is the free CatalogScan scan: it pulls a product from your /products.json feed, parses the live PDP, and reports whether the JSON-LD on that page is valid + which sub-fields (price, brand, GTIN, AggregateRating) it found.
How to fix it
Fixes ordered cheapest to most-involved:
Dawn 14 (released March 2026) emits clean Product JSON-LD with aggregateRating, full offers, and the schema.org-URL form of availability. If you're still on Dawn 12 or earlier (including most "free Shopify themes" listed pre-2026), upgrading lifts you from a partial-credit shape to a full-credit shape with no other work. Theme update, swap to the new theme as live, done.
sections/main-product.liquid20 minfreeFor custom or modified themes: open sections/main-product.liquid, find the existing <script type="application/ld+json"> block, confirm the top-level @type is "Product". If it's missing entirely, paste in a Liquid-templated Product block — every variable you need ({{ product.title }}, {{ product.price | money_without_currency }}, {{ product.featured_image | image_url }}) is in the standard product object.
If you can't or don't want to touch theme code, apps in the Shopify App Store ("JSON-LD for SEO," "Schema App," "Smart SEO") will inject a Product JSON-LD block via Liquid. Read reviews carefully — some apps inject malformed blocks that fail signal #15 (JSON-LD validity) and zero you out anyway. Test in Google's Rich Results Test after install.
Pro reads your live PDP, identifies which sub-fields are missing or malformed, and writes a Liquid snippet you can paste into sections/main-product.liquid with one click — including the GTIN, MPN, and brand-entity fields most apps skip. See Pro pricing.
5 mistakes we keep finding
1. @type: "Organization" on the PDP, no Product block
Some themes emit only an Organization graph and assume Shopify "handles the rest." Shopify does not auto-inject Product JSON-LD; if your theme doesn't emit it, no one does. Search the page source for "Product" with quotes — if you don't find it, you're failing this signal regardless of how clean the rest of the page looks.
2. One Product per variant on a multi-variant PDP
A common Liquid loop emits one Product node per variant — five sizes = five Products on one PDP. Agents don't know which one is canonical and dedupe-or-discard varies by agent. Use a single ProductGroup with a hasVariant array. Full long-form: ProductGroup JSON-LD on Shopify.
3. Smart-quotes from a review-app injection break the JSON
Apps like Yotpo and Judge.me inject review excerpts into JSON-LD via string concatenation. An unescaped curly quote (") in a customer's review breaks the JSON parse — silently. Agents skip the entire <script> block. Fix is one Liquid filter: {{ review.body | strip_html | json }}.
4. Headless Shopify (Hydrogen, Next.js) ships without JSON-LD
The default Hydrogen and Next.js Storefront-API starters do not emit Product JSON-LD by default. Re-add it explicitly in your PDP component using <script type="application/ld+json">{ JSON.stringify(productJsonLd) }</script> — server-rendered, not client-injected. Client-injected JSON-LD is invisible to most AI crawlers, which read the initial server response only.
5. availability as "InStock" instead of full schema.org URL
The strict-parser form is "availability": "https://schema.org/InStock". The short form ("InStock") is rejected by some agents and gets you half credit on signal #11 (Offers availability). Use the full URL.
See also
- The 15 signals — full reference
- ProductGroup JSON-LD on Shopify: why 60% of stores leave 18 points on the table (deep dive on multi-variant shape)
- AggregateRating: the signal 9 of 10 stores fail
- Brand entity in JSON-LD: string vs nested entity
- Full 18-signal Agentic Storefronts checklist
- Leaderboard: which 100 DTC stores pass and fail this signal
Is your store passing Product JSON-LD?
Free 2-minute scan. Paste your store URL, get a color-graded scorecard with this signal — and 14 others — checked inline.
Scan my store → See Pro auto-fix pricing