/ โ† JSON-LD Fixes / JSON-LD in Shopify
In this guide
  1. Where Shopify JSON-LD comes from
  2. Tracing a block back to its file or app
  3. The Shopify faults behind most findings
  4. A safe Liquid pattern for a custom Product block
  5. Apps, leftovers and theme updates
  6. The fix, step by step
  7. Variants, Markets and other edge cases
  8. Questions and answers

Where Shopify JSON-LD comes from

Shopify itself does not inject structured data into your storefront. Everything you see comes from the theme or from code added to it, directly or through an app.

SourceWhat it usually printsWhere to look
The theme, using the structured_data filterProduct on product pages and Article on blog posts, generated by Shopify from the object.Sections such as main-product.liquid and main-article.liquid in Dawn and themes built on it; search for structured_data.
The theme, hand-writtenOrganization and WebSite on the home page, sometimes BreadcrumbList, and on older themes a hand-built Product.Search for ld+json in layout/theme.liquid, sections/header.liquid and the snippets folder.
Apps via app embeds or app blocksProduct enhancements, AggregateRating and Review from review apps, FAQ, breadcrumbs, local business details.Theme editor, then the App embeds panel; also app blocks inside product sections.
Code added by a developer or an older appAnything; often a second Product or Organization block.Custom snippets rendered from theme.liquid or product templates.
Tag manager or script tagsBlocks injected in the browser after the page loads.Only visible in the rendered page, not in view source.

The structured_data filter is convenient because Shopify keeps its output in line with the product data, but you cannot edit what it prints. If it omits something you need, the choice is to supplement it with a separate block that shares the same @id, or to replace it with your own Liquid.

Tracing a block back to its file or app

The JSON Checker lists every block it found on the page. To connect each one to its source:

  1. Compare view source with the rendered page. Blocks present in view source (Ctrl+U) come from Liquid, so they live in the theme or an app embed. Blocks that only appear in the developer tools Elements panel were added by JavaScript.
  2. Search the theme code. In the admin go to Online Store, Themes, then the three-dot menu on your theme and Edit code. Use the search to find ld+json and structured_data. Each hit is a candidate; compare its property names with the block on the page.
  3. Toggle app embeds. In the theme editor, open App embeds and switch off one embed at a time on a copy of the theme, then preview a product page. The block that disappears belongs to that app.
  4. Look for app fingerprints. App output often carries an id or class on the script element, a comment naming the app, or property values only that app would add, such as a review count.
  5. Check for leftovers. Apps that edited theme files directly, rather than using app embeds, can leave {% render %} tags and snippets behind after you uninstall them. A block from an app you no longer use is almost always one of these.
๐Ÿ’ก Preview a copy of the theme with a product that has a quotation mark in its title, an empty vendor, a sale price and one variant out of stock. That single product exposes most of the Liquid faults below.

The Shopify faults behind most findings

FindingTypical Liquid causeCorrect pattern
Syntax error on some products only"name": "{{ product.title }}" breaks when a title contains a quote, such as a 12" planter."name": {{ product.title | json }} with no quotes of your own around it.
Control character or broken descriptionThe description is printed with its HTML and line breaks.{{ product.description | strip_html | json }}. The json filter escapes line breaks.
Image URL invalidimage_url returns an address starting with //.{{ product.featured_image | image_url: width: 1200 | prepend: 'https:' | json }}
Invalid price{{ variant.price | money }} prints a currency sign, and money_without_currency can print thousands separators or a decimal comma depending on the store's money format.{{ variant.price | divided_by: 100.0 }}, since Liquid prices are in the smallest currency unit.
Invalid currencyA hard-coded "GBP" on a store that sells in several currencies through Markets.{{ cart.currency.iso_code | json }}, so the code matches the price being shown.
Availability not recognisedPrinting true, "in stock" or the inventory number.https://schema.org/InStock or OutOfStock chosen from variant.available.
Brand missing or emptyThe template reads product.vendor, which is blank on some products or holds the store name.Fill in Vendor in the product admin, and only print brand when it is not blank.
Date format warning%z in the date filter prints +0100.{{ article.published_at | date: '%Y-%m-%dT%H:%M:%S%:z' }}
Missing offers on collection pagesA snippet that prints Product is also rendered in product cards.Only render the Product snippet from the main product section.

A safe Liquid pattern for a custom Product block

If you replace the theme's output with your own, build every value through the json filter and print optional properties only when they have a value. This snippet is a starting point for a store selling in one currency with simple variants. Save it as a snippet and render it once from the main product section.

{%- assign v = product.selected_or_first_available_variant -%}
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "@id": {{ canonical_url | append: '#product' | json }},
  "name": {{ product.title | json }},
  "url": {{ canonical_url | json }},
  {%- if product.featured_image %}
  "image": {{ product.featured_image | image_url: width: 1200 | prepend: 'https:' | json }},
  {%- endif %}
  "description": {{ product.description | strip_html | truncate: 500 | json }},
  {%- if v.sku != blank %}
  "sku": {{ v.sku | json }},
  {%- endif %}
  {%- if product.vendor != blank %}
  "brand": { "@type": "Brand", "name": {{ product.vendor | json }} },
  {%- endif %}
  "offers": {
    "@type": "Offer",
    "url": {{ shop.url | append: v.url | json }},
    "price": {{ v.price | divided_by: 100.0 | json }},
    "priceCurrency": {{ cart.currency.iso_code | json }},
    "availability": "https://schema.org/{% if v.available %}InStock{% else %}OutOfStock{% endif %}"
  }
}
</script>

Each optional property ends with a comma and is followed by a property that always prints, so a skipped value never leaves a trailing comma. Rendered for a real product, the output looks like this and parses cleanly:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "@id": "https://www.example.co.uk/products/glazed-planter-12#product",
  "name": "Glazed planter 12\" sage",
  "url": "https://www.example.co.uk/products/glazed-planter-12",
  "image": "https://www.example.co.uk/cdn/shop/files/planter-sage.jpg?width=1200",
  "description": "Frost-resistant stoneware planter with drainage hole.",
  "sku": "GP-12-SAGE",
  "brand": { "@type": "Brand", "name": "Hollowmere Pottery" },
  "offers": {
    "@type": "Offer",
    "url": "https://www.example.co.uk/products/glazed-planter-12?variant=40123456789",
    "price": 34.0,
    "priceCurrency": "GBP",
    "availability": "https://schema.org/InStock"
  }
}
</script>
โš ๏ธ If you add this, remove or disable the theme's own structured_data output for Product and any app that prints a Product, or you will have two competing blocks. Shipping, returns and variant groups are covered in the Product schema guide.

Apps, leftovers and theme updates

Apps are the most common reason a Shopify page carries markup nobody on the team wrote. Keep these points in mind:

How to decide which of two competing blocks should win is covered in the competing schemas guide. This page is about finding and repairing them on Shopify.

The fix, step by step

Step 1

Count the blocks on each page type

Run the JSON Checker on the home page, one collection, one product, one blog post and one standard page. For each, note how many JSON-LD blocks appear, which types each contains and which have errors. Use a product with a quote in its title, a sale price and an out-of-stock variant, because a plain product often hides the faults that affect the rest of the catalogue.

Step 2

Trace each block to a theme file or app

Open Edit code on the theme and search for ld+json and structured_data, then match each result to a block on the page. For anything left over, open App embeds in the theme editor and switch embeds off one at a time on a copy of the theme. Blocks missing from view source but present in the rendered page come from scripts or a tag manager.

Step 3

Switch off the copies you do not need

Decide which source owns each type, for example the theme for Product and Article and the review app for ratings only. Turn off duplicate output in app settings, disable unused app embeds, and remove render tags left by apps you have uninstalled. Aim for one block per entity on each page, so that every error you fix afterwards is in code or settings that actually count.

Step 4

Repair the Liquid that prints bad values

In the blocks you keep, pass every text value through the json filter and remove your own quotes around it. Strip HTML from descriptions, add prepend: 'https:' after image_url, print prices with divided_by: 100.0 rather than a money filter, and read the currency from cart.currency.iso_code. Wrap optional properties in conditions so blank values are left out of the block instead of being printed empty.

Step 5

Fill the product data the markup reads

Many findings are data gaps rather than code faults. Export products and look for blank Vendor, SKU, barcode and featured image fields, then complete them in bulk through a CSV import or the bulk editor. Only use barcodes that really belong to the product. Where the theme reads metafields for extra properties, make sure those metafields are filled for every product that uses the template.

Step 6

Test on a copy theme before publishing

Make all edits on a duplicate of the live theme and use its preview link to run the JSON Checker and the Rich Results Test on each page type. Only publish the copy once every block parses and the required fields are present. Afterwards, request indexing for a few key products in Search Console and use Validate fix on the Product snippets and Merchant listings reports.

Variants, Markets and other edge cases

Questions and answers

1. Does Shopify add Product schema automatically?

Shopify itself does not inject structured data into the storefront, but most current themes, including Dawn and themes built on it, print Product markup on product pages using the structured_data Liquid filter. Older and third-party themes may hand-write their own block instead. Apps can add more on top. View source on a product page and search for application/ld+json to see exactly what your store prints and how many blocks there are.

2. Why does my Shopify product schema break only on some products?

Almost always because the template inserts product text without escaping it. A title or description containing a double quote, such as a measurement in inches, ends the string early, and a description with line breaks adds control characters. Products without those characters parse fine, which makes the problem look random. Passing every text value through the Liquid json filter, instead of wrapping it in quotes yourself, fixes all of them at once.

3. How do I stop two Product blocks appearing on my product pages?

Find both sources first: typically the theme structured_data output and an SEO or review app. Then decide which one owns the Product and disable the other, either with the app settings or by removing the theme output on a copy of the theme. If a review app only needs to add ratings, check whether it can merge into the existing block by using the same @id instead of printing a separate Product.

4. Why is the price in my Shopify schema wrong or invalid?

Liquid stores prices in the smallest currency unit, so a price of 3400 means 34.00 pounds. Templates that print the money filter add a currency symbol, and money_without_currency follows the store money format, which can add thousands separators or a decimal comma. Divide the price by 100.0 to print a plain number, and read the currency from cart.currency.iso_code so it always matches the price and currency shown to the shopper on the page.

5. Will a theme update remove my JSON-LD fixes?

It can. Updating a theme from the theme store installs a new version, and edits you made inside its files are not merged in automatically. Keep custom markup in a separate, clearly named snippet so it is easy to copy across, record which files you changed, and re-run the JSON Checker on each page type after any update. The same applies when you switch to a different theme altogether, since each theme handles structured data differently.

6. I removed an app but its schema is still on my pages. Why?

Apps that work through app embeds stop printing when they are uninstalled, but older apps often edited theme files directly, adding render tags and snippets that stay behind. Those leftovers may still print markup, sometimes with stale or broken data. Search the theme code for the app name and for snippets you do not recognise, remove both the render tag and the snippet file on a copy of the theme, then test before publishing it.

7. Can I test JSON-LD changes without affecting my live store?

Yes. Duplicate the live theme from the Themes page, make your edits in the copy and open its preview. The preview link lets you view source and run the JSON Checker on real product pages without customers seeing anything. Google testing tools can only fetch public URLs, so for them paste the rendered code rather than the address. Publish the copy only once every page type passes, and keep the old theme as a fallback.

See every JSON-LD block your store prints

The JSON Checker parses every JSON-LD block on your page, validates the syntax, checks each type against schema.org, verifies required fields, detects conflicting types and runs the result against Google rich result criteria.

Open JSON-LD Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All JSON-LD Fixes  ·  JSON Syntax Errors  ·  Image Schema Problems  ·  Debugging Schema on Shopify  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.