What you will verify
You will identify existing Product JSON-LD owners, add Shopify’s product structured-data output only when needed, and verify that title, variant, price, currency, availability, SKU, canonical URL, and reviews agree with the public page.
Do not begin by pasting a second Product block. Duplicate ownership is a more common Shopify failure than having no JSON-LD at all.
Public cases that define the risk
Observed July 27, 2026:
| Store | Machine-readable condition | Why it matters |
|---|---|---|
| The Woobles | two Product offers said InStock while page said sold out | a valid graph can publish wrong commerce facts |
| Rhode | two size Product entities had different prices but the same observed SKU | variant identity can be ambiguous |
| HexClad UK | Product name ended with Default Title |
internal Shopify labels can leak publicly |
| Gymshark | no Product entity detected in initial JSON-LD response | returned and browser-rendered output may differ |
The goal is one coherent product graph, not the highest number of schema fields.
Before changing the theme
Save a baseline for four fixtures:
- a simple one-variant product
- a multi-variant product
- an in-stock product
- a sold-out product
url='https://example.com/products/example-product'
curl -sSL "$url" -o before.html
rg -n 'application/ld\+json|ProductGroup|"@type"[[:space:]]*:[[:space:]]*"Product"' before.html
In a browser, inventory scripts:
[...document.querySelectorAll('script[type="application/ld+json"]')]
.map((node, index) => {
try { return { index, value: JSON.parse(node.textContent) }; }
catch (error) { return { index, error: error.message }; }
});
Assign every block to the theme, review app, SEO app, or client-side integration.
Exact Shopify Admin path
Online Store → Themes → duplicate the live theme → Edit code
Search for:
application/ld+json
structured_data
schema.org
aggregateRating
If an authoritative Product graph already exists, repair it instead of adding another.
Add Shopify’s maintained output
Shopify documents the structured_data Liquid filter for product and article objects. It can output Product for a product without variants and ProductGroup when variants exist.
Add this once in the product template or product section only if no authoritative equivalent exists:
{% if request.page_type == 'product' and product != blank %}
<script type="application/ld+json">
{{ product | structured_data }}
</script>
{% endif %}
Do not wrap the filter output in json; it already returns structured JSON text.
Preserve visible product evidence
The graph should agree with:
product.title- selected or first available variant
- current market currency
- selected variant price
- selected variant availability
- SKU or barcode where maintained
- canonical product URL
Do not manually add a rating unless the same rating and review population are visible on the page.
Avoid Default Title
A simple Shopify product commonly has one internal variant called Default Title. If custom JSON-LD constructs a name from product plus variant title, conditionally omit it.
{% assign current_variant = product.selected_or_first_available_variant %}
{% capture public_name %}{{ product.title }}{% endcapture %}
{% unless current_variant.title == 'Default Title' %}
{% capture public_name %}{{ product.title }} — {{ current_variant.title }}{% endcapture %}
{% endunless %}
Use this only in a deliberately maintained custom graph. Prefer Shopify’s maintained filter where it meets the store’s needs.
Expected result
- one parent product identity
- recoverable variants with stable identifiers
- correct current offer for each selected variant
- no internal labels
- no unsupported aggregate rating
- canonical URL and market context agree
Common failure modes
- theme and SEO app each emit a complete Product
- review app emits a second stale Offer
- first variant price is used after another variant is selected
- compare-at price is presented as current price
- inventory policy is confused with actual availability
- localized page exposes another market’s currency
Verify public output
curl -sSL -H 'Cache-Control: no-cache' "$url" -o after.html
diff -u before.html after.html
Test all four fixtures. Validate meaning before syntax: the most important check is agreement with the buy box.
Rollback
Publish from a duplicate theme. Retain the previous theme version and document any app embed disabled during consolidation.
Primary source: Shopify structured_data filter.
Community discussion
Add to the article
Ask a technical question, share a storefront result, or challenge a conclusion with evidence.