Schema Drift: When Your JSON-LD Quietly Starts Lying
Structured data is how AI assistants read your site. When the JSON-LD stops matching the page, you are feeding them false information — and nothing warns you.
Structured data has quietly become the most important thing on a page that nobody looks at. Search engines and AI assistants use JSON-LD to work out what a page is — the product, the price, the author, the answer. Which means when your JSON-LD stops matching your page, you are not simply missing an optimisation. You are actively feeding machines false information about your business, and nothing in your workflow will tell you.
What schema drift looks like
Drift happens when the structured data and the visible content come from different sources of truth, and only one of them gets updated.
- A price is changed in the CMS. The visible page updates; the hardcoded
offers.pricein the JSON-LD does not. - An article is rewritten and re-dated, but
datePublishedstill reflects the original. aggregateRatingclaims 47 reviews while the page shows none, because the review section was removed in a redesign.- Opening hours in
LocalBusinessschema still describe last year's schedule. - A service is renamed in the database while the schema keeps the old name, splitting your entity in two.
Every one of these is invisible in a browser. The page looks perfect.
Why it costs more than it used to
When structured data only drove rich snippets, drift meant losing a star rating. Now it feeds the systems that summarise and cite you. An AI assistant that reads a contradiction between your markup and your content has no way to resolve it, and the safe behaviour is to trust you less or skip you.
The rating case is worse than a lost snippet: aggregateRating markup with no corresponding visible reviews is a documented cause of manual actions. That is not a missed opportunity, it is a penalty.
The root cause is architectural
Drift is almost never carelessness. It is what happens when JSON-LD is authored as a separate artefact — pasted into a template, managed by a plugin, or written once during a launch — instead of being generated from the same data the page renders.
The fix is to make the two physically incapable of disagreeing. If the price on the page and the price in the schema both read from the same model attribute, there is no drift to detect, because there is only one value.
Generate, never author
In a Laravel application this means building schema from the model, in one place, and rendering it from that. Something like:
public function schema(): array
{
return [
'@context' => 'https://schema.org',
'@type' => 'Service',
'name' => $this->title,
'description' => $this->summary,
'provider' => ['@type' => 'Person', 'name' => config('app.name')],
];
}
The page heading prints $service->title. The schema prints $service->title. They cannot diverge.
Test what you cannot generate
Some values will always come from elsewhere — a review count from a third-party API, an aggregate computed in a different service. For those, add a test that parses the JSON-LD out of the rendered response and asserts it against the rendered content:
it('keeps service schema consistent with the page', function () {
$service = Service::factory()->create(['title' => 'ERP Development']);
$html = $this->get("/services/{$service->slug}")->getContent();
preg_match('#<script type="application/ld\+json">(.*?)</script>#s', $html, $m);
$schema = json_decode($m[1], true);
expect($schema['name'])->toBe($service->title)
->and($html)->toContain($service->title);
});
Run it in CI and drift becomes a failing build instead of a slow leak. This is the same instinct as any other regression test: the value of the check is not that it passes today, it is that it fails the day someone changes one side and not the other.
An audit you can do this week
- List every page type that emits structured data.
- For each, ask where every value comes from — the model, or a human typing it once.
- Anything in the second category is a drift candidate. Move it to the first, or write a test.
- Run your key URLs through Google's Rich Results Test and compare each field against what the page actually shows. Not whether it validates — whether it is true.
Validation tools check that your schema is well-formed. Almost nothing checks that it is honest. That gap is where drift lives.