Switching SEO plugins guide
Find Custom Schema That Will Not Survive an SEO Plugin Import
Trace where each structured data node on your site comes from, capture the graph before and after a staged switch, and decide which nodes to rebuild by hand.
On this page
Assume that no structured data moves with an SEO metadata import. Before the switch, capture the JSON-LD on one URL of each template, work out which plugin or file produced each node, and run the switch on staging to see which nodes disappear. Then rebuild only the nodes that describe visible content you still want to mark up.
This guide is about keeping structured data through a plugin replacement. Whether a given type can produce a rich result, and how to fix validation errors, are separate topics; the retired types are covered in Google’s Retired Rich Results.
Where each schema node comes from
A single page can carry nodes from several sources. What happens at the switch depends on the source, not on the node type.
| Origin | Typical example | When the old SEO plugin is deactivated |
|---|---|---|
| The SEO plugin’s generated graph | Organization, WebSite, WebPage, Article, BreadcrumbList | Stops printing; the new plugin prints its own graph |
| Per-post schema settings in the SEO plugin | A schema type choice, a schema template, a custom schema builder | Stops printing; not read by WP Visibility’s importer |
| The SEO plugin’s content blocks | A FAQ or how-to block that feeds the plugin’s graph | Markup stops; visible text may remain (see SEO Plugin Blocks After Deactivation) |
| The theme or another plugin | Product, event, or review markup from a store or events plugin | Keeps printing, and may now sit beside a second graph; WooCommerce’s own footer markup is the exception (see below) |
| Hand-written markup | A script in a Custom HTML block, a header injection plugin, or a theme file | Keeps printing, but can point at @id values that no longer exist |
WP Visibility’s importer reads titles, descriptions, canonicals, social fields, and indexing choices. It does not read schema types, schema templates, or custom schema from any source. The migration docs list this under “What stays behind”, for example in Migrate from Rank Math.
Other vendors document their own paths. Rank Math’s Yoast migration guide, checked September 27, 2026, describes a converter for Yoast FAQ and HowTo blocks into Rank Math’s blocks. That is a Rank Math feature for a Rank Math destination; it does not describe what any other plugin does.
Capture the old and new graphs
Pick one URL per template: the homepage, a blog post, a page, a category, a product if you sell online, and every page where someone added schema by hand. Save this script as schema-nodes.py:
import json, re, sys, urllib.request
PATTERN = r'<script[^>]*application/ld\+json[^>]*>(.*?)</script>'
for url in sys.argv[1:]:
html = urllib.request.urlopen(url).read().decode("utf-8", "replace")
for block in re.findall(PATTERN, html, re.S | re.I):
try:
data = json.loads(block)
except ValueError:
print(url, "INVALID JSON", "", sep="\t")
continue
nodes = data.get("@graph", [data]) if isinstance(data, dict) else data
for node in nodes:
if isinstance(node, dict):
print(url, node.get("@type"), node.get("@id", ""), sep="\t")
Run it against production before anything changes, and against staging after the switch:
python3 schema-nodes.py https://garden.example/ https://garden.example/how-to-prune/ > schema-before.tsv
python3 schema-nodes.py https://staging.garden.example/ https://staging.garden.example/how-to-prune/ > schema-after.tsv
Also save the full HTML of each URL. The node list tells you what changed; the saved markup tells you what the removed nodes contained. To find hand-written markup, search the content too:
wp db query "SELECT ID, post_title FROM wp_posts WHERE post_content LIKE '%application/ld+json%' AND post_status='publish'"
Replace wp_ with your table prefix. Check header and footer injection plugins and the theme’s templates as well.
What WP Visibility generates
With its Schema module on (the default), WP Visibility 2.10.3 prints one JSON-LD graph per page, except on search results and the 404 page:
| Node | Where | @id |
|---|---|---|
| Organization or Person | Every page | https://your-site/#organization or #person |
| WebSite | Every page | https://your-site/#website |
| WebPage, CollectionPage, or ProfilePage | Every page, by context | the page URL plus #webpage |
| BreadcrumbList | Every page | the page URL plus #breadcrumb |
| BlogPosting, or the article type chosen in the editor, with its author | Blog posts | the page URL plus #article |
| Product, or ProductGroup for a variable product | WooCommerce product pages, with the WooCommerce module on (the default) | the page URL plus #product |
It also removes WooCommerce’s own footer JSON-LD, which carries the store’s product markup, so the Product node is not printed twice.
The editor’s Structured data panel offers a Schema type choice (Automatic, Article, BlogPosting, NewsArticle, WebPage) and an Advanced field for raw JSON-LD that is added to that post’s graph. WP Visibility never generates FAQPage, HowTo, or a search box action on its own. The publisher node is set up in Set Your Site Identity to an Organization or a Person.
Decide what needs manual rebuilding
Compare schema-before.tsv with schema-after.tsv and give each missing node one decision.
| Missing node | Usual decision |
|---|---|
| Organization, WebSite, WebPage, BreadcrumbList, Article on a blog post, or a WooCommerce Product | Covered by the new graph; check the facts in it match the old ones |
| LocalBusiness, Event, Recipe, Article on a page or custom post type, a Product outside WooCommerce, or similar | Rebuild, if the page visibly shows that content |
| FAQPage or HowTo | Keep the visible content; see the retired results guide before rebuilding markup |
| A node that described content no longer on the page | Leave it out |
Google’s structured data policies, checked September 27, 2026, say not to mark up content that is not visible to readers of the page, and that correct markup does not guarantee a rich result. So a node passing validation is not a reason to keep it; matching the visible page is.
Also look at nodes that remain. Markup from a theme or another plugin now sits beside WP Visibility’s graph; WooCommerce’s own footer markup is the exception described above. Two Organization nodes with different names, or two BreadcrumbList nodes with different trails, are worth resolving at their source.
Rebuild a node in the Advanced field
For a node that belongs to one post or page:
- Open the post, then the WP Visibility sidebar, then Structured data.
- Paste a single JSON object with an
@type, or a JSON array of such objects, into Advanced. The field says whether the JSON is valid; invalid JSON is not saved. - Do not paste a whole
{"@context": ..., "@graph": [...]}wrapper. In 2.10.3, an object with no@typeof its own is not added to the page. - Replace references to the old plugin’s
@idvalues. Pointpublisherorproviderathttps://your-site/#organization(or#personif the site represents a person), andisPartOfat the page URL plus#webpage. - Save, reload the page source, and run the URL through the Schema Markup Validator or Google’s Rich Results Test.
The Advanced field is in the block editor sidebar for posts and pages, and for public custom post types that use the block editor and support custom fields. It is not in the classic editor box or on category archives. For markup across many URLs, developers can add nodes with the wpvis_schema_graph filter instead of editing each post.
Checklist
- One URL per template captured before the switch, with full HTML saved.
- Each node’s origin identified: SEO plugin, block, theme or plugin, or hand-written.
- Before and after node lists compared on staging.
- Every missing node given a decision, based on visible content.
- Rebuilt nodes validated, with no references to old
@idvalues.
For the rest of the switch, follow How to Switch SEO Plugins With a Verification Plan.
