Use the same @id every time your structured data refers to the same person, organization, article, or product. That lets separate JSON-LD blocks identify the same thing rather than repeating disconnected descriptions.
The JSON-LD data model defines @id as a node identifier. An object containing only that identifier refers to the same node; it does not supply all of the node's properties.
Connect an article to its author
These abbreviated examples show the difference. They illustrate author references, rather than complete Recipe markup.
The first recipe includes an author name without a reusable ID:
// Fragmented: each page defines an isolated Recipe entity
{
"@context": "https://schema.org",
"@type": "Recipe",
"name": "Sourdough Starter",
"author": { "@type": "Person", "name": "Jane Baker" }
}
The second uses the identifier also used for that author's description:
// Connected: Recipe references a Person that exists on the author page
{
"@context": "https://schema.org",
"@type": "Recipe",
"name": "Sourdough Starter",
"author": { "@id": "https://example.com/authors/jane-baker#person" }
}
Keep the accurate author name and any required properties in the published data too. A reference does not mean a search engine will visit another page to fill in missing rich-result properties.
Generate consistent IDs in Laravel
Use the canonical URL plus a stable fragment, such as #article or #person, to distinguish the entity from the page that describes it:
public function articleJsonLd(Article $article): array
{
return [
'@context' => 'https://schema.org',
'@type' => 'Article',
'@id' => route('articles.show', $article) . '#article',
'headline' => $article->title,
'author' => [
'@id' => route('authors.show', $article->author) . '#person',
],
'publisher' => [
'@id' => route('home') . '#organization',
],
'mainEntityOfPage' => [
'@id' => route('articles.show', $article),
],
];
}
Check that your route helpers produce the public hostname and preferred URL form. A staging host, extra slash, or different author URL can create a second identifier for the same entity.
Reuse the same convention in Go
This example generates organization, person, and article descriptions from consistent URLs:
type EntityGraph struct {
BaseURL string // canonical site URL, with no trailing slash
OrganizationName string
OfficialProfiles []string
}
func (g *EntityGraph) Organization() map[string]interface{} {
return map[string]interface{}{
"@context": "https://schema.org",
"@type": "Organization",
"@id": g.BaseURL + "#organization",
"name": g.OrganizationName,
"url": g.BaseURL,
"sameAs": g.OfficialProfiles,
}
}
func (g *EntityGraph) Person(name, profileURL string) map[string]interface{} {
return map[string]interface{}{
"@context": "https://schema.org",
"@type": "Person",
"@id": profileURL + "#person",
"name": name,
"affiliation": map[string]interface{}{
"@id": g.BaseURL + "#organization",
},
}
}
func (g *EntityGraph) TechArticle(slug, title, authorURL string) map[string]interface{} {
return map[string]interface{}{
"@context": "https://schema.org",
"@type": "TechArticle",
"@id": g.BaseURL + "/" + slug + "#article",
"headline": title,
"author": map[string]interface{}{
"@id": authorURL + "#person",
},
"publisher": map[string]interface{}{
"@id": g.BaseURL + "#organization",
},
}
}
Set OrganizationName and OfficialProfiles from the real publisher. Include only profiles that belong to that publisher. The example assumes BaseURL has no trailing slash and that article slugs produce their canonical URLs; use your application's URL helper if routing is more complicated.
The descriptions can appear together in an @graph, or use the same identifiers in page-specific blocks. Include the properties needed for each rich-result type on the page where you want that result.
Find IDs that do not line up
When auditing the output, distinguish a full description from an object that only references an ID. Look for staging hosts, conflicting descriptions, different entity types sharing an ID, and references without a description in the pages you checked.
This small Python example collects described and referenced IDs from one response:
import json, re, requests
from urllib.parse import urljoin
def audit_entity_graph(start_url):
response = requests.get(start_url, timeout=10)
response.raise_for_status()
jsonld_blocks = re.findall(
r'<script type="application/ld\+json">(.*?)</script>',
response.text, re.DOTALL
)
described_ids = set()
referenced_ids = set()
for block in jsonld_blocks:
try:
data = json.loads(block)
described, referenced = collect_ids(data)
described_ids.update(described)
referenced_ids.update(referenced)
except json.JSONDecodeError:
continue
unresolved = referenced_ids - described_ids
return {
'described_entities': len(described_ids),
'referenced_entities': len(referenced_ids),
'unresolved_references': list(unresolved),
}
def collect_ids(obj):
described, referenced = set(), set()
if isinstance(obj, dict):
node_id = obj.get('@id')
if node_id:
target = referenced if set(obj) == {'@id'} else described
target.add(node_id)
for key, value in obj.items():
if key != '@id':
child_described, child_referenced = collect_ids(value)
described.update(child_described)
referenced.update(child_referenced)
elif isinstance(obj, list):
for item in obj:
child_described, child_referenced = collect_ids(item)
described.update(child_described)
referenced.update(child_referenced)
return described, referenced
The sample's script-tag extraction assumes the exact markup shown in its regular expression. Use an HTML parser when auditing varying attributes, quotation marks, or script formatting. It also skips invalid JSON, so report those parse failures separately in a production audit.
A reference may be described on another page. An unresolved ID in this one-page result is a reason to inspect further, not proof that Google cannot understand the page. Run the relevant rich-result validation separately.
Check overlapping WordPress output
A theme and multiple SEO plugins may each emit Organization, WebSite, Person, and Article markup. Inspect the rendered scripts and decide which component should produce each description. Use the plugin's filters or settings to align identifiers before adding another script in wp_head.
Stable IDs keep descriptions consistent. They do not establish expertise, topical authority, or a ranking advantage by themselves.
