The same schema type declared twice on one page
What Is This Issue
The same schema @type is declared more than once on a single page — two Organization
blocks, two WebSite blocks, two BreadcrumbList blocks — leaving search engines to
choose between competing descriptions of the same thing.
This is nearly always accidental. It happens when structured data is added from more than one place and nobody checks the combined output:
- An SEO plugin emits
Organizationand the theme emits it again - A page template and a layout template both add
BreadcrumbList - A tag manager injects schema that the server already rendered
- A component is rendered twice on a page that reuses it in a header and a footer
Repeating a type is not always wrong. A listing page legitimately carries many Product
or Article entries, and those belong inside an ItemList or a @graph rather than as
separate top-level blocks. The problem is duplicate declarations of a page-level
singleton — the one organisation the site belongs to, the one breadcrumb trail to this
page, the one website it is part of.
Why Is This Important
Duplicate declarations force search engines to guess, and a guess is not something you can rely on for a rich result.
- Conflicting facts win at random. If one
Organizationblock gives a logo and a phone number and the other gives neither, which one Google keeps is not documented and not stable. Your knowledge-panel details can change without you changing anything. - Rich results are dropped rather than merged. Two
BreadcrumbListblocks describing different trails is ambiguous enough that Google commonly shows neither. - Entity resolution suffers. Two
Organizationnodes with no shared@idcan be read as two organisations, splitting the signals that should reinforce one entity. - It hides drift. Two sources of truth diverge over time. The plugin gets updated, the theme does not, and the page now asserts two different company names.
- It is a symptom worth reading. A duplicated type usually means two systems are both managing your structured data without knowing about each other, which is a problem larger than this page.
How XeoPix Detects This
-
Fetches the page and parses the raw HTML.
-
Collects every JSON-LD block — each
<script type="application/ld+json">element — and parses each one. Blocks that fail to parse are reported separately as invalid JSON-LD and take no part in this check. -
Flattens the schemas. Objects inside a
@graph, and entries in a top-level array, are counted individually, because a graph of three nodes is three declarations. -
Counts each
@typeacross everything the page declares. -
Raises one finding per type that appears more than once, naming the type and the number of instances — for example
Duplicate schema type found: Organization (2 instances).
The check counts declarations, not whether their contents agree. Two identical
Organization blocks and two contradictory ones both report here, because both mean two
systems are writing the same schema and the page has no single source of truth.
Nested types are counted where they are declared. A Product containing an Offer and an
AggregateRating declares three types once each, not one type three times.
Note: detection uses raw HTML only — no JavaScript is executed. A duplicate created by a client-side injection on top of server-rendered markup will not be seen here, so if you inject schema from a tag manager, check the rendered DOM as well.
How To Fix
-
Find every source. The finding names the duplicated type and how many instances were found. Search your rendered HTML for
"@type": "<TheType>"— the count tells you how many places to look for. -
Work out which system emits each one. In practice the pairs are almost always: SEO plugin plus theme, layout template plus page template, or server-rendered markup plus a tag-manager injection.
-
Pick one owner per type. Decide where each page-level schema comes from and turn the others off. Most SEO plugins have per-type switches for exactly this reason.
-
Merge rather than delete, if both carry real data. Combine the properties into one block instead of dropping whichever you found second:
{ "@context": "https://schema.org", "@type": "Organization", "@id": "https://example.com/#organization", "name": "Acme Ltd", "logo": "https://example.com/logo.png", "telephone": "+44 20 7946 0000" } -
Use
@graphand@idfor pages that genuinely need several related schemas. One script, one graph, each node with a stable@idso they can reference each other instead of repeating each other. -
For lists, nest instead of repeating. Many products or articles on one page belong inside an
ItemList, not as many sibling top-level blocks. -
Re-scan and confirm the type appears once.
What We Store
For each page, XeoPix stores:
rawSchemas— every JSON-LD block that parsed, as structured data.schemaTypes— the distinct@typevalues the page declares.typeCount— how many times each type was declared.
For each finding, the audit issue’s details holds:
message— the duplicated type and its instance count, for exampleDuplicate schema type found: Organization (2 instances).
The duplicated blocks are not stored side by side for comparison. The types and counts are enough to locate them in your page source, and storing full schema payloads twice over would mean retaining page content the scan does not need.
Examples
Two blocks declaring the same singleton
<!-- Emitted by the SEO plugin -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Acme Ltd",
"url": "https://example.com"
}
</script>
<!-- Emitted again by the theme, with different facts -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Acme Limited",
"logo": "https://example.com/logo.png"
}
</script>Two names, one logo, no way to tell which Google will keep.
Merged into one
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"@id": "https://example.com/#organization",
"name": "Acme Ltd",
"url": "https://example.com",
"logo": "https://example.com/logo.png"
}
</script>Several related schemas, one graph
Use @id so the nodes reference each other instead of repeating each other.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://example.com/#organization",
"name": "Acme Ltd"
},
{
"@type": "WebSite",
"@id": "https://example.com/#website",
"url": "https://example.com",
"publisher": { "@id": "https://example.com/#organization" }
},
{
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Home", "item": "https://example.com" },
{ "@type": "ListItem", "position": 2, "name": "Shop", "item": "https://example.com/shop" }
]
}
]
}
</script>A listing page: nest, do not repeat
<!-- Reported: Product declared 3 times at the top level -->
<script type="application/ld+json">{ "@type": "Product", "name": "Widget A" }</script>
<script type="application/ld+json">{ "@type": "Product", "name": "Widget B" }</script>
<script type="application/ld+json">{ "@type": "Product", "name": "Widget C" }</script><!-- One list, which is what a collection page actually is -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "ItemList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "item": { "@type": "Product", "name": "Widget A" } },
{ "@type": "ListItem", "position": 2, "item": { "@type": "Product", "name": "Widget B" } },
{ "@type": "ListItem", "position": 3, "item": { "@type": "Product", "name": "Widget C" } }
]
}
</script>References
- Google — Structured data general guidelines — Google Search Central
- JSON-LD 1.1 — Named graphs — W3C
- Schema.org — ItemList — Schema.org
- Rich Results Test — Google Search Central