Skip to content

WordPress systems / Case 25

Reconciling custom WordPress schema with Yoast without duplicate entities

Staging and production were describing the same pages differently, and custom JSON-LD could overlap with the schema graph already generated by the SEO plugin.

WordPressYoast SEOJSON-LDSchema.orgTechnical SEO

What this solved for the business or user

Search engines should receive one coherent description of the business, page, location, and professional content. The work removed contradictory or duplicated structured data while keeping the information the site genuinely needed to expose.

What was happening

A schema audit found that staging and production did not output the same entity set. One environment had a simpler WebPage and Organization graph, while the other also emitted service, local-business, and professional-person entities. Some of those blocks were hard-coded in the theme footer while Yoast already generated its own base graph.

Why the obvious solution was not enough

Deleting every custom schema block would remove useful entities, but keeping independent JSON-LD scripts could duplicate WebPage or Organization data and create IDs that did not connect to Yoast's graph. The solution had to preserve the real entities while giving one system clear ownership of each shared schema piece.

How the solution works

  1. Inventory every JSON-LD source first: SEO plugin output, theme hooks, template-specific printers, and hard-coded footer scripts.
  2. Compare equivalent staging and production URLs so environment drift is visible before changing the code.
  3. Assign ownership for base entities. When Yoast is active, avoid printing a second independent WebPage or Organization unless there is a specific reason to replace it.
  4. Move custom schema printers into focused modules for entities the site genuinely needs, such as service, local-office, or professional-person data.
  5. Use stable @id values and relationships so custom pieces connect to the existing site graph instead of becoming unrelated JSON-LD islands.
  6. Gate optional entities on the content or configuration that makes them true. Do not output a LocalBusiness or professional entity only because the template supports one.
  7. Validate the final rendered source and test the canonical URL after cache clears. Structured-data debugging must inspect what the crawler receives, not only what the PHP function intends to return.
Avoid duplicating Yoast-owned page schema
if (defined('WPSEO_VERSION')) {
    // Let Yoast own the base WebPage / Organization graph.
    add_filter('mytheme_print_custom_webpage_schema', '__return_false');
}

// Keep only genuinely additional entities in focused printers.
add_action('wp_footer', 'mytheme_print_location_entity_schema', 20);

What should be verified before shipping

  • Compare rendered JSON-LD on equivalent staging and production pages.
  • Verify WebPage, WebSite, and Organization are not printed twice with competing IDs.
  • Confirm optional service, location, or professional entities appear only on pages where the visible content supports them.
  • Run Google's structured-data validation tools after clearing page and CDN caches.
  • Inspect source HTML as a crawler would receive it; do not validate only a PHP array or admin preview.

What changed

Structured data became easier to reason about because the SEO plugin owned the base graph and the theme only added entities that represented real page content. Staging and production could then be compared against the same schema contract.

Reusable lesson

Structured data is a graph, not a collection of independent scripts. Decide who owns the shared entities first, then extend that graph deliberately instead of printing another version of the same page.

Official documentation and standards