/ โ† AI Schema Generator Fixes / Duplicate Schema
In this guide
  1. How generated markup ends up doubled
  2. Why it matters, and what it does not do
  3. Finding every block, including hidden ones
  4. Deciding who owns each type
  5. The fix, step by step
  6. Merging instead of deleting
  7. Platform notes
  8. Questions and answers

How generated markup ends up doubled

The generator reads one page and writes a complete description of it. It cannot see that your plugin already describes the same page, so it writes its own version of everything it thinks belongs there. Duplicates then arrive by a few routes.

What a clash looks like

A recipe blog on WordPress, fictional Hedgerow Kitchen, has this from its SEO plugin:

{ "@type": "Article", "headline": "Blackberry and apple crumble",
  "author": { "@id": "https://example.co.uk/#/schema/person/1" },
  "datePublished": "2026-08-30T08:00:00+01:00" }

And this from a generated block pasted into the post:

{ "@type": "BlogPosting", "headline": "The best blackberry and apple crumble",
  "author": { "@type": "Person", "name": "Hedgerow Kitchen Team" },
  "datePublished": "2026-08-30" }

Two articles, two headlines, two different authors. Search engines have to decide which to believe, and neither of them is the Recipe markup that would actually help the page.

Why it matters, and what it does not do

Having several JSON-LD blocks on a page is normal and allowed. Google reads them all, and a page can quite properly carry a WebPage, a BreadcrumbList, an Organization and a Recipe from different sources. Google does not penalise duplicate markup as such. The trouble comes when two blocks describe the same thing and disagree.

SymptomCause
Rich Results Test lists two Product or two Article itemsTwo sources describe the page's main entity separately
Warnings on one item and a clean pass on the otherOne block is complete and one is a partial copy
Search results show a different price or rating from the pageAn older generated block carries values the live data has moved on from
Two organisations with slightly different namesThe generator wrote its own Organization instead of linking to the plugin's
Merchant Center price mismatch warningsThe feed matches one Product block and Google read the other

In short, duplicates waste the effort of generating markup. They make it harder to tell which description is authoritative, and they double the places you have to update when a price, author or date changes.

Finding every block, including hidden ones

View source (Ctrl+U, or Cmd+Option+U on a Mac) and search for application/ld+json to count the blocks in the HTML your server sends. That misses anything added by JavaScript, so also check the rendered page. Open developer tools, go to the Console and run:

document.querySelectorAll('script[type="application/ld+json"]').forEach((s, i) => {
  try {
    const d = JSON.parse(s.textContent);
    const nodes = d['@graph'] || (Array.isArray(d) ? d : [d]);
    console.log(i, nodes.map(n => n['@type']).join(', '));
  } catch (e) { console.log(i, 'unreadable JSON'); }
});

This prints each block's number and the types inside it, whether it came from the server or a script. If the console shows more blocks than view-source did, the extra ones are injected by JavaScript, often from a tag manager or an app.

The Rich Results Test also renders the page and lists detected items by rich result type. Two entries for the same type on a page that should have one is the clearest sign of a clash.

๐Ÿ’ก To see which system wrote a block, look at the HTML just above it. Plugins often leave a comment such as a plugin name and version, and theme blocks usually sit inside the section the theme controls. A block in the middle of the article content was almost certainly pasted in.

Deciding who owns each type

The durable fix is a simple rule: each type on a page has exactly one source. Decide it once for the whole site, per template, and write it down.

TypeUsual ownerRole for the generator
WebSite, WebPage, BreadcrumbListSEO plugin or themeNone. Remove these from generated output.
Organization, author PersonSEO plugin, with fixed @id valuesReference by @id only.
Article, BlogPostingSEO pluginUsually none; occasionally extra properties merged in.
Product with price and stockShop platform or theme, from live dataEnrich with descriptive properties only if the platform omits them.
Ratings and reviewsThe review app that displays themNone.
Recipe, Event, Course, JobPosting, ServiceOften nobodyThe best use of generated markup.

When the generator's version of a type is clearly better than your plugin's, you can reverse the ownership: turn the plugin's output for that type off and keep the generated one. Just do not run both.

The fix, step by step

Step 1

Count the JSON-LD blocks on the live page

Open the published page, not the editor preview, and list every structured data block using view source for server-rendered markup and the console snippet for anything added by scripts. Note the number of blocks and the types inside each. Then run the URL through the Rich Results Test and check whether any rich result type is detected twice. Keep this list, because you will compare against it when you finish.

Step 2

Map each block to the system that writes it

For each block, work out its source: the SEO plugin, the theme, the shop platform, a review or events app, a tag manager, or a generated block pasted into the content or a header field. Plugin comments, the position in the HTML and switching a plugin off on a staging copy all help. A block you cannot trace is usually a forgotten paste or a tag manager tag, so check both before moving on.

Step 3

Choose one owner for each type

For every type that appears more than once, pick the source that should keep it. Prefer the one that reads live data, such as the shop platform for price and stock, and the one that already manages site-wide identifiers, such as the SEO plugin for Organization and authors. Choose the generated version only where it is clearly more complete and accurate. Record the decision per template so the next person follows it.

Step 4

Strip the generated block down to what is missing

Edit the generated JSON-LD so it only contains types no other source provides. Delete generated WebSite, WebPage, BreadcrumbList and Organization nodes, and any Article or Product the plugin or platform already writes. Where the remaining node needs an author, publisher or parent page, replace the inline copy with a reference to the existing @id. What is left is usually a single focused node such as a Recipe or Event.

Step 5

Switch off the losing source

Where you decided the generated markup should own a type, turn that type off in the plugin, theme or app rather than leaving both running. Use the plugin's per-post schema settings, the theme's template file or the app's structured data option. Remove old pasted blocks from post content, header script fields and tag manager tags. Make changes on a staging copy first where you can, since theme edits can remove more than you intend.

Step 6

Retest the rendered page

Clear any page cache, then repeat the count from step 1 on the live page and run the Rich Results Test again. Each rich result type should now appear once, with no conflicting values between blocks. Compare the remaining values with the visible page. Check one more page from each template you changed, because a theme or plugin setting affects every page that uses it, not only the one you tested.

Merging instead of deleting

Sometimes neither source is complete. The plugin's Article has the right author and dates, and the generated block adds a useful about or mentions list. You can combine them without creating a second article by giving the generated node the same @id as the plugin's node. JSON-LD treats nodes with the same identifier as one entity, so the properties are read together.

A cleaner approach, where the plugin allows it, is to add the extra properties through the plugin's own filter or custom fields so one block contains everything. If you do merge by @id, the two nodes must not disagree: a property given different values in each becomes a conflict, not a merge.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Recipe",
      "@id": "https://example.co.uk/blackberry-apple-crumble/#recipe",
      "name": "Blackberry and apple crumble",
      "mainEntityOfPage": { "@id": "https://example.co.uk/blackberry-apple-crumble/" },
      "author": { "@id": "https://example.co.uk/#/schema/person/1" },
      "publisher": { "@id": "https://example.co.uk/#organization" },
      "image": ["https://example.co.uk/images/crumble-4x3.jpg"],
      "recipeYield": "6 servings",
      "prepTime": "PT20M",
      "cookTime": "PT40M",
      "recipeIngredient": ["500 g cooking apples", "250 g blackberries", "150 g plain flour", "100 g butter", "75 g demerara sugar"],
      "recipeInstructions": [
        { "@type": "HowToStep", "text": "Peel, core and slice the apples, then mix with the blackberries in a baking dish." },
        { "@type": "HowToStep", "text": "Rub the butter into the flour, stir in the sugar and scatter over the fruit." },
        { "@type": "HowToStep", "text": "Bake at 190C for 40 minutes until golden." }
      ]
    }
  ]
}
</script>

This block is what the generator's output looks like after step 4: only the Recipe, with the author, publisher and page linked to the @id values the SEO plugin already publishes. The identifiers shown are placeholders; copy the real ones from your own page source.

Platform notes

WordPress

Yoast SEO, Rank Math and All in One SEO each let you change the schema type per post and switch off article markup for a post type in their settings. Yoast also offers a developer filter, wpseo_json_ld_output, that turns its structured data off entirely, which is only sensible if something else will provide WebSite, WebPage and Organization. Page builders and recipe plugins may add their own blocks too, so check them before assuming the SEO plugin is the only source. Generated blocks pasted in a Custom HTML block are stored in the post content and survive plugin changes, which is why they are easy to forget.

Shopify

Many themes output Product markup in the product template, often in a section or snippet file, and review apps commonly add their own ratings markup. Before adding a generated Product, check the theme code for existing application/ld+json output and the review app's settings for a structured data option. Keep price and availability with whichever source reads live product data, so sales and stock changes are reflected automatically.

Custom and headless sites

Search the codebase for application/ld+json. Layout components often emit site-wide markup that page components then repeat. A single helper that builds one @graph per page, with each type added in one place, prevents duplicates by design.

โš ๏ธ Switching off an SEO plugin's schema output removes everything it wrote, not just the type you were worried about. Check the page afterwards and make sure WebSite, Organization and breadcrumbs are still provided somewhere.
Questions and answers

1. Is it bad to have more than one JSON-LD script on a page?

No. Several blocks from different sources are normal and Google reads all of them. A page can properly carry breadcrumbs from a plugin, an organisation from the theme and a recipe from a generated block. The problem is two blocks describing the same thing, such as two Articles or two Products for one page, especially when their values differ. Count the types, not the scripts: each thing the page is about should be described once.

2. Will Google penalise my site for duplicate schema?

Google does not treat duplicate markup as a violation in itself, so you should not expect a manual action simply for having two Article blocks. The cost is confusion. Google has to choose between conflicting headlines, authors or prices, may show the less accurate one, and reports warnings against the weaker copy. Clean markup with one description per entity is easier to maintain and removes the risk of the wrong price or rating appearing in search results.

3. Should I remove my SEO plugin's markup or the generated block?

Usually the generated block, or at least the parts of it that repeat what the plugin does. SEO plugins manage site-wide identifiers for your organisation and authors and update dates automatically, while a pasted block is a fixed snapshot. Keep the generated markup for types the plugin does not produce, such as Recipe, Event or Course. Switch the plugin's version off only when the generated one is clearly better and you are prepared to maintain it.

4. Why does the Rich Results Test show two products when I only added one?

Something else on the page is also writing Product markup. On Shopify the theme's product template and review apps are the usual sources; on WooCommerce the shop plugin and an SEO plugin may both output it. Use the console snippet in this guide to list every block with its types, then trace each one to its source. Keep the version that reads live price and stock data and remove or disable the other.

5. Can I combine a generated block with my plugin's markup?

Yes, if they describe the same entity with the same identifier. Give the generated node the @id the plugin already uses and the properties are read together as one entity. This only works when the two nodes do not contradict each other; different values for the same property become a conflict. Where your plugin supports adding properties through its own settings or filters, putting everything in one block is simpler to maintain.

6. The generator keeps adding Organization and WebSite. Should I keep them?

Not if your SEO plugin or theme already outputs them, which is the case on most WordPress sites. Delete those nodes from the generated block and reference the existing organisation by its @id where the remaining markup needs a publisher or brand. Site-wide entities should come from one place so their name, logo and profiles are identical on every page. If nothing else on your site provides them, define them once in your template instead.

7. How do I find schema added by Google Tag Manager?

Tag manager markup is added by JavaScript after the page loads, so it does not appear in view source. Use the browser console snippet in this guide, which lists blocks in the rendered page, or run the Rich Results Test, which renders scripts before reading structured data. If a block appears there but not in the source, open your tag manager container and look for custom HTML tags containing application/ld+json, then remove or update them.

Generate only the markup you are missing

The AI Schema Generator reads your page content with an AI model, writes JSON-LD tailored to the content type and validates it against schema.org and Google rich result criteria, ready for you to fit around your existing markup.

Open AI Schema Generator Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All AI Schema Generator Fixes  ·  Organization and Person Reference Loss  ·  Using AI Schema in WordPress  ·  Using AI Schema on Shopify Product Pages  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.