How to find and fix broken JSON-LD structured data

Short answer

JSON-LD is structured data written as JSON inside a <script type="application/ld+json"> tag. JSON is strict: a trailing comma, curly quotes around a name, or an unescaped " in a value makes the whole block unreadable, so search engines can't use any of it. Paste the snippet below into the Console to parse every block and see the line with the error, then fix the template that writes it.

Broken JSON vs incomplete schema

Structured data can go wrong in two very different ways:

What's wrongWhat happensHow to find it
Broken JSONThe text isn't valid JSON: a stray comma, a wrong quoteNothing in the block can be readThe snippet below, or any JSON parser
Incomplete schemaValid JSON, but missing properties a rich result needs, such as price on an offerThe data is read, but the page may not get a rich resultGoogle's Rich Results Test (the Schema Markup Validator checks general schema.org use, not Google's requirements)

This guide is about the first kind. Fix it first: there's no point checking properties in a block nothing can read. Even valid, complete structured data doesn't guarantee a rich result. Google decides whether to show one.

Here's what a broken block looks like, with the error Chrome gives for it:

1  {
2    "@context": "https://schema.org",
3    "@type": "Product",
4    "name": "Blue Widget",
5    "sku": "BW-01",
6  }
Expected double-quoted property name in JSON at position 103 (line 6 column 1) The error points at line 6, but the mistake is the comma at the end of line 5: JSON doesn't allow a comma after the last item.

Parsers report where they gave up, which is often just after the real mistake. Look at the line before as well.

The usual mistakes

MistakeLooks likeChrome says
Trailing comma"sku": "BW-01", then }Expected double-quoted property name
Missing comma"@type": "Product" then "name": …Expected ',' or '}' after property value
Curly quotes around a name“name”: "Blue Widget"Expected double-quoted property name (or Expected property name or '}' if it's the first one)
Unescaped quote in text"name": "The "Blue" Widget"Expected ',' or '}' after property value
Line break in textA product description pasted across two linesBad control character in string literal
Empty value"price": , when the template had no priceUnexpected token ','
JavaScript value"sku": undefinedUnexpected token 'u'
Comments// product dataExpected property name or '}', or Unexpected token '/'
Two objects in one block{…} followed by {…}Unexpected non-whitespace character after JSON

Almost all of these come from templates, not people. A product name with a " in it, a description with a line break, or a price field that's sometimes empty only breaks the block on the pages that have that data. So one product page can be broken while the next is fine.

One more to watch for: the text </script> inside any value ends the script tag early, even inside a string. Escape it as <\/script> or \u003c/script>. Both are valid JSON and read back as the same text.

How to check by hand

1Parse every block with a snippet 30 seconds

Open DevTools (F12), go to the Console and paste:

// Parses every JSON-LD block on the page and shows where any syntax error is
const blocks = [...document.querySelectorAll('script[type="application/ld+json" i]')];
const rows = blocks.map((script, i) => {
  const text = script.textContent.trim();
  try {
    const data = JSON.parse(text);
    const items = [].concat(data).flatMap(d => (d && d['@graph']) || [d]);
    const types = items.map(d => d && d['@type']).flat().filter(Boolean);
    return { block: i + 1, result: 'valid JSON', types: types.join(', ') || '(no @type)', near: '' };
  } catch (e) {
    const pos = Number((e.message.match(/position (\d+)/) || [])[1]);
    const line = text.slice(0, pos).split('\n').length;
    const near = Number.isInteger(pos) && text ? 'line ' + line + ': ' +
      text.slice(Math.max(0, pos - 50), pos).replace(/\s+/g, ' ') + ' ▶' + text.slice(pos, pos + 20).replace(/\s+/g, ' ') : '';
    return { block: i + 1, result: 'ERROR: ' + e.message.slice(0, 120), types: '', near };
  }
});
console.table(rows.length ? rows : [{ result: 'No JSON-LD on this page' }]);

It lists each JSON-LD block. Valid ones show their @type (Product, Organization, BreadcrumbList…). Broken ones show the error and the text just before where the parser stopped (marked ▶). The real mistake is usually just before the arrow.

It reads the page as rendered, so it includes blocks added by JavaScript, which Google can read too (see server HTML vs rendered page). It only checks the JSON is valid, not that the schema is complete.

2Use Google's Rich Results Test 2 minutes

Paste the URL into Google's Rich Results Test. A syntax error shows up as "Structured data with syntax errors detected", often listed as an item of unknown type, because the parser stopped before it could tell what the block was. Once the JSON is valid, the same test lists missing or invalid properties for each rich result type.

3Check other pages built from the same template 5 minutes

Because errors depend on each page's data, test a few pages with unusual content: products with quotes or symbols in the name, long descriptions, no price, no reviews. In Search Console, the rich result reports under Enhancements (and Shopping for products) and the Unparsable structured data report, which appears only when Google finds some, list affected pages across the site.

How to stop it happening again

  1. Build the JSON with a JSON encoder, not by gluing strings together in the template. In PHP that's json_encode, in JavaScript JSON.stringify, in Liquid the json filter. They escape quotes and line breaks for you. PHP's json_encode and Liquid's json filter also make </script> safe. If you use JSON.stringify in a server template, replace < with \u003c yourself.
  2. Leave out empty fields rather than writing "price": , or "price": "".
  3. Don't hand-edit JSON-LD in a word processor or CMS rich text field. They turn straight quotes into curly ones.
  4. Keep one object per block, or wrap several in an array, or use @graph.

How crawlers report this

If a crawl sent you here, these are the labels to look for. Our guide to Screaming Frog and Sitebulb issues covers other common ones.

Screaming FrogSitebulbWhat it means
Structured Data: Parse ErrorsNo direct matchThe structured data isn't valid JSON, so it can't be read at all.

Doing this on every page?

SEOSnip checks your JSON-LD for you

Open the extension on any page and it parses every JSON-LD block. You get:

  • One issue for the page that counts the broken blocks and quotes the first one
  • The lines around the error, with the line the parser stopped on marked
  • The parser's error message, so a developer can find it straight away
  • A ticket with that evidence, and a "Check if fixed" button that re-runs the check

It checks the JSON is valid. Use the Rich Results Test for whether the schema is complete.