How to find and fix broken JSON-LD structured data
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 wrong | What happens | How to find it | |
|---|---|---|---|
| Broken JSON | The text isn't valid JSON: a stray comma, a wrong quote | Nothing in the block can be read | The snippet below, or any JSON parser |
| Incomplete schema | Valid JSON, but missing properties a rich result needs, such as price on an offer | The data is read, but the page may not get a rich result | Google'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 }
Parsers report where they gave up, which is often just after the real mistake. Look at the line before as well.
The usual mistakes
| Mistake | Looks like | Chrome 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 text | A product description pasted across two lines | Bad control character in string literal |
| Empty value | "price": , when the template had no price | Unexpected token ',' |
| JavaScript value | "sku": undefined | Unexpected token 'u' |
| Comments | // product data | Expected 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
- Build the JSON with a JSON encoder, not by gluing strings together in the template. In PHP that's
json_encode, in JavaScriptJSON.stringify, in Liquid thejsonfilter. They escape quotes and line breaks for you. PHP'sjson_encodeand Liquid'sjsonfilter also make</script>safe. If you useJSON.stringifyin a server template, replace<with\u003cyourself. - Leave out empty fields rather than writing
"price": ,or"price": "". - Don't hand-edit JSON-LD in a word processor or CMS rich text field. They turn straight quotes into curly ones.
- 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 Frog | Sitebulb | What it means |
|---|---|---|
| Structured Data: Parse Errors | No direct match | The 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.
JSON-LD structured data is invalid
JSON-LD block · head > script:nth-of-type(3)
Expected double-quoted property name in JSON at position 103 (line 6 column 1)
3 | "@type": "Product", |
4 | "name": "Blue Widget", |
5 | "sku": "BW-01", |
6 | } |