One of the less discussed problems in structured data implementations is entity identity fragmentation.
The issue is not invalid Schema.org.
The issue is that different components generate different identifiers (@id) for the same real-world entity.
Example
A website defines its author as:
{
“@type”: “Person”,
“@id”: “https://example.com/#person”
}
The site owner references this identifier from Book, Article, Organization, and other entities.
On the same page, Yoast SEO generates:
{
“@type”: “Person”,
“@id”: “https://example.com/#/schema/person/7fd8301b398510be4f8ffd0fd2382315”
}
Both nodes represent exactly the same person.
Both validate.
Both are correct according to Schema.org.
However, they are different graph nodes.
Every reference generated by Yoast (publisher, author, WebSite.publisher, etc.) points to its internal identifier instead of the site’s existing canonical identifier.
The same situation may occur with any persistent entity, including Organization, Product, Book, Article, LocalBusiness, or WebSite, whenever multiple structured data generators independently create identifiers for the same object.
Why this happens
Most WordPress plugins generate structured data independently.
Examples include:
- Yoast SEO
- Rank Math
- WooCommerce
- custom themes
- custom plugins
Each component is responsible for its own graph generation.
None of them manages entity identity across the entire site.
As a result, every component is free to invent its own @id values.
Each generated graph is internally consistent.
The fragmentation appears only after the graphs are merged into the final page.
Why validation doesn’t detect it
Schema validators verify:
- syntax
- required properties
- allowed values
They do not verify whether two Person nodes describe the same real-world entity while using different identifiers.
From the validator’s perspective, both graphs are valid.
From the perspective of a knowledge graph, identity has been duplicated.
Validators verify schema correctness.
They do not verify graph identity consistency.
Practical consequence
Suppose a site contains:
- Person
- Organization
- Book
- Article
- Product
If each plugin creates its own Person node, the graph now contains multiple identifiers for one individual.
The consumer must infer that they represent the same entity.
Nothing in the generated graph guarantees that outcome.
Instead of following a single deterministic graph, the consumer must perform entity resolution using signals such as url, sameAs, name, and surrounding graph relationships.
The more structured data generators contribute to the page, the more identity reconciliation becomes necessary.
Current workarounds
The only practical solution today is custom integration.
Developers must intercept the generated structured data, replace internally generated @id values with canonical identifiers, and update every reference pointing to the original identifier.
The process must be repeated for every structured data generator participating in the graph.
The missing capability
Current WordPress schema generators generally lack a mechanism to declare:
This is the canonical identifier for the Person entity. Reuse it everywhere.
Instead, each generator maintains its own identity namespace.
An architectural alternative would be a shared entity registry allowing generators to request the canonical identifier for an entity before producing structured data, rather than minting new identifiers independently.
The result is syntactically correct structured data with fragmented entity identity.
The problem is not Schema.org.
The problem is the absence of canonical entity identity management across multiple structured data generators.
Example: Overriding Yoast’s generated Person @id
/**
* Tell Yoast to include those fields in Person.sameAs.
*/
add_filter( 'wpseo_schema_graph', function( $graph ) {
$old = null;
// Find the Person node.
foreach ( $graph as &$piece ) {
if ( ! isset( $piece['@type'] ) ) {
continue;
}
$types = (array) $piece['@type'];
if ( in_array( 'Person', $types, true ) ) {
$old = $piece['@id'];
$piece['@id'] = 'https://example.com/#person';
break;
}
}
if ( ! $old ) {
return $graph;
}
// Replace every reference to the old @id.
array_walk_recursive( $graph, function( &$value ) use ( $old ) {
if ( $value === $old ) {
$value = 'https://example.com/#person';
}
} );
return $graph;
}, 999 );

Leave a Reply