Googlebot and JSON-LD: a single unescape pass
EN

Googlebot and JSON-LD: a single unescape pass

Last verified: August 30, 2026
12 min read
Guide
PageSpeed 100/100
500+ WP projects

Google has stopped repairing your structured data. JSON-LD extraction now applies a single pass of HTML unescaping, so a block that used to be quietly straightened out simply fails to parse and disappears. There is no error in Search Console, no warning, only a rich result that stops showing up. This piece shows how to measure your own corpus in a few minutes instead of guessing, where that encoding comes from in WordPress, and why a one-off audit is not enough. Our own measurement across 68 055 blocks is in here, along with the script.

#What Google actually changed

The statement is short and worth quoting whole, because everything else follows from one sentence:

To bring our parser up to JSON and other standards, we changed our JSON-LD extraction and are now only applying a single pass of HTML unescaping

Gary Illyes added a pointer to where correctness is defined: RFC 8259, the JSON specification. That is not an SEO guideline, it is a reference to the standard every JSON parser in the world already follows.

The practical consequence is stated just as briefly: double-escaped entities, such as & or ✔, will no longer be unrolled. Google’s parser used to make an extra pass and straighten that out. It now makes one pass and is left with text that is not valid JSON.

It is worth naming what the announcement does not contain. There is no rollout date. There is no link to updated Google documentation. The source is a LinkedIn post, reported by Search Engine Roundtable on 21 August 2026. Treat it as a state Google described, not as a specification you can cite back to a client.

#What double escaping is, and where it comes from

Take a simple case: the company name “Smith & Sons” in a JSON-LD name field.

written aswhat the JSON parser seesstatus
"Smith & Sons"Smith & Sonsvalid, JSON does not require escaping an ampersand
"Smith & Sons"Smith & Sonsvalid, universal escape
"Smith & Sons"Smith & Sonsparses, but the value is wrong
"Smith & Sons"Smith & Sonsthis is double escaping

An ampersand on its own will not blow up the block, because JSON does not require it to be escaped. The real trouble starts with the quotation mark. If a template writes " where \" belongs, one unescape pass leaves you with " rather than a quote character. The string never closes and the block stops being JSON.

Where does it come from in WordPress? Almost always from processing the same value twice. Content passes through esc_html() on save, then through a theme filter on output, and finally lands in a JSON-LD field that would have escaped it properly on its own. Each of those steps is correct in isolation. Composed together they produce an encoding that worked for years only because Google was straightening it out for you.

#How to check your own corpus in five minutes

The rule that matters most: check the built HTML, not the template source. The source always looks fine, because the escaping is added on output. If you ship a static build, scan the output directory. On a classic WordPress install, pull a sample of URLs with wget or curl and scan what the server actually returned.

The check itself is a dozen lines. Pull out every <script type="application/ld+json"> block, try to parse it, and separately look for the double-entity pattern:

const RE = /<script[^>]*type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi;
let m;
while ((m = RE.exec(html))) {
  const body = m[1];
  if (/&amp;(quot|amp|lt|gt|#\d+);/.test(body)) report("double entity", file);
  try { JSON.parse(body); } catch (e) { report("does not parse: " + e.message, file); }
}

Two separate tests, because they catch different things. JSON.parse will fail on a broken string, but it will happily accept &amp;amp; inside a value that is still valid JSON and merely contains rubbish. The entity pattern catches exactly that second case: the block parses, and the rich result shows &amp; where a character belongs.

A third test worth adding is single HTML entities in values. Those are not errors, but after this change they will be unrolled exactly once, so the result may differ from what you saw before. Better to know they are there.

#Our measurement: 68 055 blocks, zero failures

We ran this scan across our own corpus on 30 August 2026, against a freshly built production output.

metricresult
pages with at least one JSON-LD block15 742
JSON-LD blocks in total68 055
blocks that fail to parse as JSON0
pages with a double-escaped entity0
pages with a single HTML entity inside JSON-LD0

Zero in all three categories. We are not writing that as a boast but as information about what the result means and what it does not. Our stack generates structured data from frontmatter through Astro components and injects it with the pattern set:html={JSON.stringify(...)}. JSON.stringify produces valid JSON by definition, and set:html adds no HTML escaping of its own. In other words: we did not get zero because we were careful, we got it because that particular pattern has no way to produce double escaping.

That is more useful than the number itself. If your stack assembles JSON-LD by concatenating strings in a template, or through a plugin that drops a content field into a prepared chunk of JSON, the risk is real and your result will differ. Measure yours; do not copy ours.

#Where this breaks most often in WordPress

Three sources recur in the audits we run for clients.

The first is an SEO plugin filling description from a field that has already been through wp_kses or esc_attr. It usually breaks on apostrophes and quotation marks, which is why it shows up faster in languages that use them heavily in ordinary prose.

The second is a hand-written JSON-LD block pasted into header.php or into theme options, where values are inserted with echo and no wp_json_encode. That is the common variant in bespoke themes built a few years ago, and the hardest to find, because it appears in no plugin screen.

The third is a page builder that stores content with HTML entities already in the database. Then even a correctly written JSON-LD generator receives text containing &amp; on input and dutifully encodes it a second time.

The common denominator is always the same: a value escaped twice, once for HTML and once for JSON, by two layers that do not know about each other.

#How to encode it correctly

The rule is unambiguous, because there is a standard for it. Inside a JSON value, escape the JSON way, not the HTML way.

In PHP that means wp_json_encode() over the whole structure, never hand-assembled strings. In JavaScript, JSON.stringify(). In an Astro template, the pattern we use ourselves:

<script type="application/ld+json" set:html={JSON.stringify(schema)} />

If you genuinely need a character that could close the script block early, use a universal escape. & for an ampersand and < for a less-than sign are safe in every JSON parser and require no HTML knowledge from whoever reads the data.

What not to do: never put an HTML entity into a JSON value hoping something will unroll it. For years that something was Google. From now on it unrolls exactly once, and every other consumer of your structured data, from Bing to an AI assistant reading the page, was never obliged to do it at all.

#A one-off audit is not enough, make it a gate

This is the part most often skipped. Structured data is not prose you write once. A template, a plugin or an integration generates it, and any update to those can reintroduce the escaping. An audit run today tells you about today’s build and nothing more.

We turned the scan into a gate that runs after the build. It reads the output directory, counts blocks, tries to parse each one, and fails only on a real problem, meaning a block that does not parse or a double entity. With no output directory present it exits zero, so it does not produce a false red in an environment where nobody has built yet.

Three details decide whether such a gate is worth anything. First, it has to read the artefact rather than the source, because the source proves nothing about escaping. Second, it has to count blocks and print that count, so somebody notices when the number drops from sixty-eight thousand to two hundred because an integration stopped emitting them. Third, it has to separate an error from a note: single HTML entities get reported, but they do not fail the build, because that is not an outage, it is something you should know about.

#What to do when the scan finds something

A scan gives you a list of files, not a diagnosis. Before fixing anything, work out which layer produces the encoding, because a fix in the wrong place comes back with the next update.

Take one URL from the list and look at the raw server response, for example curl -s URL | grep -A5 "application/ld+json". Check whether the damaged value comes from the post title, the SEO description or a custom field. That points at the layer faster than reading code does.

Then decide by source:

  1. If the value comes from an SEO plugin, check whether two plugins are generating the same schema type. A duplicate generator is a more common cause of strange values than a bug in either one alone.
  2. If the block is pasted into the theme, rewrite it to wp_json_encode() over the whole array. Manual string concatenation is the only real error here and it cannot be patched halfway.
  3. If the entities are already in the database because a page builder put them there, do not fix it in the generator. Decode the value once before handing it to the JSON encoder, for example with html_entity_decode() using the quote flag and an explicit UTF-8 charset.

The classic mistake looks like this:

echo '{"name":"' . esc_html( $title ) . '"}';

Correct looks like this:

echo wp_json_encode( array( 'name' => $title ) );

The difference is not the character count, it is who owns the escaping. In the first version an HTML function does it in a context that is not HTML. In the second a JSON encoder does it in a JSON context.

After the fix, rerun the scan against a new build rather than the old artefact. That sounds obvious, and half of all “I fixed it and it is still broken” reports are a scan of a stale output directory.

#What you actually lose by ignoring it

Losing schema does not hurt immediately, and that is the worst part. There is no overnight ranking drop, there is a gradual disappearance of the things that made the result stand out: review stars, product data, an FAQ list, breadcrumbs. You see the effect in click-through rate rather than position, and click-through rate falls slowly and is easy to attribute to something else.

The second consumer of this data is newer and less forgiving. Answer engines read structured data because it is the cheapest way to establish what a page is without interpreting the whole body. On our own site agent traffic is now measurable and not marginal: on the order of a hundred visits a day, two thirds of them through our own MCP endpoint. Those systems have no reason to repair somebody else’s escaping. Google repaired it for years out of politeness towards the web as it found it. A new consumer of your data never had that habit and will not acquire it.

The third layer is Search Console, and here it is worth being honest about what you will not learn. Rich result reports will show a drop in valid items, but they will not say “this block failed to parse because of a double entity”. You will see that items went missing and you will have to work out why yourself. That is why a scan on your side is worth the few minutes: it gives you the cause rather than the symptom.

#What we do not know, and what to do anyway

We do not know the rollout date. We do not know whether the change affects all schema types equally, or whether Search Console will report a lost entity at all rather than simply ceasing to show the rich result. There is no updated documentation to send a client. Those are real gaps and it is better to say so than to pretend a LinkedIn post carries the weight of a specification.

Despite the gaps, the operational decision is simple and depends on none of the missing information. Valid JSON was valid back when Google was fixing errors on your behalf too. Scan your built HTML, repair whatever fails to parse, replace HTML entities in values with JSON escapes, and wire the test into your process so it cannot come back. If the result comes out at zero, as ours did, that is a result as well: you know your generator has no way to produce this fault, and you can stop wondering about it every time a plugin updates.

The biggest risk in this story is not the change itself, it is that the change is silent. No alert arrives. The rich result just stops appearing, and three months later a report shows a visibility decline with no obvious cause. Five minutes of scanning today is cheaper than that investigation.

Next step

Turn the article into an actual implementation

This block strengthens internal linking and gives readers the most relevant next move instead of leaving them at a dead end.

Want this implemented on your site?

If visibility in Google and AI systems matters, I can build the content architecture, FAQ, schema, and internal linking needed for SEO, GEO, and AEO.

Related cluster

Explore other WordPress services and knowledge base

Strengthen your business with professional technical support in key areas of the WordPress ecosystem.

Article FAQ

Frequently asked questions

Practical answers to apply the topic in real execution.

SEO-readyGEO-readyAEO-ready5 Q&A
What exactly did Google change in JSON-LD extraction?#
Google now applies a single pass of HTML unescaping instead of repairing double-escaped content. In its own words: „we changed our JSON-LD extraction and are now only applying a single pass of HTML unescaping”. Double-escaped entities, an ampersand written as &amp; rather than &, are no longer unrolled.
Will my site be affected?#
Only if a JSON-LD block actually contains double escaping or fails to parse as JSON. You check that on built HTML, not on the template source. We scanned 68 055 blocks across 15 742 pages and found zero cases, but that is a result for one stack and not a guarantee for yours.
How do I encode an ampersand or a special character correctly?#
Per RFC 8259, which is standard JSON escaping, or with a universal escape such as \u0026. Not with an HTML entity, and certainly not with an entity escaped a second time. Gary Illyes pointed directly at RFC 8259 as the definition of proper escaping.
When does the change take effect?#
Google did not give a rollout date. The statement appeared on LinkedIn and the Search Engine Roundtable write-up is dated 21 August 2026. There is no link to updated documentation either, so treat it as a state Google reported rather than something written into the docs.
Is a one-off audit enough?#
No. Structured data is produced by a template, a plugin or an integration, and any update to those can reintroduce the escaping. It is worth wiring a parse check for every JSON-LD block into a post-build gate, so a regression fails loudly instead of quietly deleting your schema.

Need an FAQ tailored to your industry and market? We can build one aligned with your business goals.

Let’s discuss

Related Articles