Skip to Content

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 Organization and 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 Organization block 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 BreadcrumbList blocks describing different trails is ambiguous enough that Google commonly shows neither.
  • Entity resolution suffers. Two Organization nodes with no shared @id can 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

  1. Fetches the page and parses the raw HTML.

  2. 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.

  3. 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.

  4. Counts each @type across everything the page declares.

  5. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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" }
  5. Use @graph and @id for pages that genuinely need several related schemas. One script, one graph, each node with a stable @id so they can reference each other instead of repeating each other.

  6. For lists, nest instead of repeating. Many products or articles on one page belong inside an ItemList, not as many sibling top-level blocks.

  7. 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 @type values 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 example Duplicate 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>

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

Last updated on