Structured Data Is a Contract, Not a Checkbox
By Trent SEO Structured Data Development 4 min read
There is a version of structured data that is entirely decorative. You install a plugin, it emits some JSON-LD, a validator turns green, and nobody looks at it again. The markup exists, it is syntactically valid, and it describes a page that may or may not resemble the one you shipped.
The useful version starts from a different premise: every property you emit is a claim, addressed to a machine, that you are committing to keep true.
Claims have consequences
datePublished: "2026-08-21" says this piece was published on that day. author says a specific person wrote it. eventStatus: EventScheduled says a conference is happening as announced. citation says these specific URLs are the sources this page rests on.
Each of those is checkable against reality, which is the entire reason a consumer values them. And each of them can silently become false — not through malice, but through ordinary drift:
- A refactor changes how dates are formatted, and every
datePublishedshifts by a day. - A template is copied and the
authornode keeps pointing at the previous writer. - An event is cancelled in your database and the page still emits
EventScheduled, because the status field and the markup were wired up by different people two years apart. - A “citation” list is generated from all sources, so a conference directory now appears in your markup as a peer of the organiser’s own site.
None of these break a validator. All of them break the claim.
The two-part test
Any structured-data property worth emitting passes both halves of this:
- Is it true right now?
- What checks that it is still true next month?
Most implementations answer the first and ignore the second, which is how a page ends up confidently asserting something that was accurate at the time it was written. The markup then decays in exactly the way a comment in code decays — invisibly, and specifically in the places where the code changed.
The second half is the one that costs something, and it is the one that makes the difference between markup that is an asset and markup that is a liability.
Assert it against the source, not against a schema
Validators check shape. They will happily confirm that your Event node has a well-formed startDate. What they cannot check is whether that date is the date in your database, because they have never seen your database.
So the assertion has to run on your side, over the built output, against the records the output was built from:
if (ld.startDate !== record.startDate)
fail(`${page}: Event.startDate (${ld.startDate}) != record (${record.startDate})`)
That is a trivial check and it catches an entire family of bugs that no external validator can see: template drift, formatting regressions, off-by-one timezone coercion, a field renamed on one side of the boundary only.
The same shape works for the harder ones. If your data model distinguishes who asserted a fact from how strong the evidence is, the markup must not claim more than the record supports. If your record deliberately does not know a US state, the markup must not invent one — and “must not invent one” is an assertion you can actually write:
if (addr.addressRegion !== record.place.region)
fail(`${page}: a region the record does not carry appeared in the markup`)
That check exists to catch the helpful refactor — the one where somebody adds a lookup table so the markup is “more complete”. Completeness is not the goal. Accuracy is, and an absent field is accurate while a guessed one is not.
Two vocabularies that share a property name
The subtlest version of this failure is a naming collision between your model and schema.org’s.
Say your event records carry a lifecycle field: upcoming, active, completed, cancelled, postponed. Schema.org has eventStatus, which takes EventScheduled, EventCancelled, EventPostponed, EventRescheduled. The words overlap. The vocabularies do not: yours describes where a record sits in your tracking of it; theirs describes whether the event is happening as announced.
Pass one through as the other and you emit eventStatus: "upcoming", which is not a schema.org term and means nothing to any consumer. It validates as a string. It communicates nothing.
The fix is a translation table that lives in one place and is asserted:
if (!SCHEMA_EVENT_STATUS.includes(ld.eventStatus))
fail(`${page}: the record's lifecycle value is leaking through untranslated`)
Name your own field something that cannot be confused with theirs, and the collision stops being available.
Escaping is a security control, not a formatting concern
One more, because it is the property people most often get right by accident and then lose in a refactor.
A <script type="application/ld+json"> block is a script element. A </script> inside any string field in your payload ends it, and everything after renders as live markup. If a page’s structured data is built from content anyone else can influence, JSON.stringify alone is a stored-XSS sink.
The fix is small — escape <, > and & as \uXXXX sequences, which leaves the parsed JSON identical — but it must be the only path to that sink. One serializer, used everywhere, with a test that feeds it a hostile payload. Two serializers means one of them is the one nobody updated.
What it buys
Do all of this and structured data stops being a checkbox and becomes something closer to a public API: a stable, verified, machine-readable statement of what your pages contain, which other systems can rely on without asking you.
That is worth considerably more than a rich result. It is the difference between being indexed and being quotable.
Read next
The whole archive-
AI
What an Agent Needs Before It Will Quote You
Optimising for language models is mostly framed as writing for a new kind of reader. The more useful frame is that you are now writing for a reader that has to decide whether to stake its answer on you.
-
SEO
A Taxonomy Is a Commitment, Not a Filing Convenience
Categories, subcategories and tags look like three flavours of the same idea. They are three different promises about permanence, and confusing them is how a site ends up with two answers to what a page is about.
-
SEO
The Canonical Tag Is a Claim About Identity
Most canonical bugs are not tagging mistakes. They are a site accidentally telling search engines that all of its pages are the same page — and the defaults in every framework make that the easy thing to do.
Keep reading
More on SEO
Every vertical on this site carries its own writing, its upcoming events and its answers on one page — and the whole archive is available as a feed with a real publication date on every item.