Most WordPress sites already print some schema markup before the owner touches anything. An SEO plugin, a page builder, a review plugin or the theme itself may each output its own JSON-LD. So the real question when you want to add schema markup to WordPress is usually not "how do I add it?" but "what is already there, who is producing it, and how do I add the missing piece without a second, conflicting copy?"
This guide is for site owners, editors and developers running a self-hosted WordPress site. You will check what your pages output today, choose one source of truth, add markup in the way that suits your skills (a plugin setting, a Custom HTML block or a short piece of PHP), switch off duplicates, and test the result. It does not promise rich results. Google decides whether any page is shown with a special appearance, and valid markup is only one of the conditions.
Short answer: First view the page source and search for application/ld+json to see what your site already outputs. Then pick one source for each type of markup: a plugin setting, a block for a single page, or code in a child theme. Turn off any duplicate source, then validate with Google's Rich Results Test.
Before you begin
- Access: an administrator login, and for the code route, a way to edit files safely (a child theme or a small custom plugin) and a way to roll back.
- Backup: take a full backup first. WordPress's own documentation says to back up all files before editing, and warns that a mistake in a file edited from the dashboard takes effect immediately and can cause a blank screen.
- Time and test copy: an hour for one markup type, longer if several plugins overlap. Use a staging site if your host offers one.
What schema markup is already on my WordPress site?
Do this audit before installing anything. Open a published post and a page in a private window, view the page source, and search for application/ld+json. Each match is a block of JSON-LD. Note which @type values appear (for example Organization, WebSite, Article, BreadcrumbList) and whether the same type appears twice.
You can also paste the URL into the Rich Results Test and look at the items it detects. How to Use the Rich Results Test shows how to read the result, and JSON-LD Validation goes deeper into debugging.
Google's structured data guidelines require markup to be a true representation of the page and not to describe content visitors cannot see. The practical problem with duplicates is that two blocks can disagree: one says the business phone number is old, the other new, or two plugins describe the same article with different dates. You then have two places to maintain and no way to tell which Google used. One source per type avoids that.
Which way should I add schema markup to WordPress?
| Method | Good for | Watch out for |
|---|---|---|
| SEO plugin settings | Site-wide types: organization, website, article, breadcrumbs | Plugin defaults may differ from your real facts; review them |
| Dedicated schema plugin | Many page types, per-page forms | Often overlaps with an SEO plugin, which causes duplicates |
| Theme output | Nothing to configure | You rarely control the fields, and a theme change removes it |
| Custom HTML block | One-off pages, such as a single video or event | Manual upkeep; may be stripped for users without the right capability |
| Code in a child theme or custom plugin | Exact control, template-driven values | Needs a developer, testing and a backup |
For a typical brochure or blog site, an SEO plugin you already use for titles and sitemaps is the lowest-effort route. Choose code only when the plugin cannot produce the type or fields you need. This guide does not recommend a particular plugin; confirm any plugin's current features in its own documentation.
Step-by-step instructions
Step 1: List the markup you need and the markup you have
Make a short table: page type, schema type, who produces it today. For instance: blog posts, Article, the SEO plugin; contact page, LocalBusiness, nobody; product pages, Product, the shop plugin. Our guides on Organization, Article, LocalBusiness, BreadcrumbList and Product markup list which fields matter for each.
You have succeeded when every row has exactly one owner, or an obvious gap.
Step 2: Decide which tool owns each type
Give each schema type to one source. If your SEO plugin already outputs Article and BreadcrumbList, keep it as the owner and fill in its settings. If you want a type it does not cover, add that type in another way, but do not let the second tool re-describe the types the first one owns.
You have succeeded when no two tools are set to output the same type on the same page.
Step 3A: Use a plugin setting
Open the plugin's schema or search appearance settings. Enter your real organization name, logo, social profile links and, for a local business, the same name, address and phone number that appear on the contact page. Pick the default article type for posts. Save, clear any page cache, and reload the page source.
You have succeeded when the page source shows one block of each type with your values.
Step 3B: Use a Custom HTML block for one page
For a single page, such as a video page, add a Custom HTML block (type /html in a new paragraph to insert one) and paste the JSON-LD with its script wrapper. WordPress's documentation says that since version 7.0 users who lack the unfiltered_html capability, a Contributor for example, have the block's content sanitized and disallowed tags such as script stripped on save. So check the live source after an editor without that capability saves the page; the markup may be gone. The page's own content is also the wrong place for markup you want applied to many pages, because someone must remember to copy it.
For the contents of a video block, see Video Schema Markup.
You have succeeded when the live page source, not just the editor preview, contains the block.
Step 3C: Add JSON-LD with code in a child theme
If you edit a theme, edit a child theme. WordPress's developer documentation says changes made directly in a theme are overwritten when the theme updates, and that a child theme lets you keep modifications while still receiving parent updates. A small site-specific plugin is another good home because the code then survives a theme change.
The wp_head action prints output inside the page's head section, so it is the usual place to hook. This example outputs Article markup on single posts only:
<?php
// In the child theme's functions.php, or a small custom plugin.
add_action( 'wp_head', 'example_article_json_ld' );
function example_article_json_ld() {
if ( ! is_singular( 'post' ) ) {
return;
}
$post = get_queried_object();
if ( ! $post instanceof WP_Post ) {
return;
}
$data = array(
'@context' => 'https://schema.org',
'@type' => 'Article',
'headline' => html_entity_decode( wp_strip_all_tags( get_the_title( $post ) ), ENT_QUOTES, 'UTF-8' ),
'datePublished' => get_post_time( 'c', true, $post ),
'mainEntityOfPage' => get_permalink( $post ),
'author' => array(
'@type' => 'Person',
'name' => get_the_author_meta( 'display_name', $post->post_author ),
),
);
$image = get_the_post_thumbnail_url( $post, 'full' );
if ( $image ) {
$data['image'] = array( $image );
}
echo '<script type="application/ld+json">' . wp_json_encode( $data ) . '</script>' . "\n";
}
What each choice does and why:
is_singular( 'post' )limits the output to single posts, so archives and the home page do not get an article block.get_queried_object()returns the post being displayed. The code passes the author ID toget_the_author_meta()because that function needs an ID outside the loop.get_the_title()returns a filtered title, and plugins may add markup to it, so the code strips tags before use.get_post_time( 'c', true, $post )formats the publish time in GMT as an ISO 8601 date (PHP'scformat).wp_json_encode()is WordPress's wrapper aroundjson_encode(). Let it build the JSON instead of joining strings by hand, so quotes in a title cannot break the block.- The image line adds the featured image URL only when one exists, because the function returns false otherwise.
Test on staging first. A syntax error in functions.php can take the site down, which is why the backup comes first.
You have succeeded when a post's page source shows a single Article block with the right headline, date and author.
Step 4: Switch off the duplicates
Go back to your audit. For each type produced twice, turn one source off. In plugins, look for a setting that disables schema output or a specific type. If a theme prints its own markup and you cannot disable it from settings, the theme's documentation or its developer is the place to ask; do not hack the parent theme, because an update will undo it. Clear caches after each change, including any caching plugin and CDN.
You have succeeded when view-source shows each type once per page.
Step 5: Validate the markup
Run the Rich Results Test on one URL per page type: a post, a page, a product if you sell, and the home page. Fix critical errors first. Missing recommended fields are not errors but are worth reviewing against the Google documentation for that type. Also check the rich result status reports in Search Console after deployment, as Google suggests. Google notes that pages can break after deployment through templating or serving issues, which is why a post-launch check matters.
You have succeeded when each page type passes the test and shows no duplicates.
Step 6: Add a recheck to your routine
Plugin updates, theme changes and page builders all change markup silently. Add a source check of two or three key URLs to your release routine; our WordPress SEO Checklist is a good home for it. Crawvix's structured data check also reads the JSON-LD blocks on pages it samples, flags blocks that do not parse and lists missing fields for common types. It does not read Microdata or RDFa, and a pass there is not a prediction about search appearance.
You have succeeded when markup is checked after every significant change.
Final verification checklist
- You viewed the live page source and listed every JSON-LD block on a post, a page and the home page.
- Each schema type is produced by exactly one source.
- Names, addresses, phone numbers and dates match the visible page.
- Code changes live in a child theme or custom plugin, with a backup taken first.
- A Custom HTML block, if used, still has its script in the live source after saving.
- The Rich Results Test shows no critical errors for each template.
- Caching layers were cleared before testing.
- The way you add schema markup to WordPress is written down so the next person knows which tool owns what.
Common mistakes to avoid
- Installing a second schema plugin without turning off the first. Run the source audit before and after.
- Marking up content that is not on the page. Reviews, FAQs or prices that visitors cannot see break Google's guidelines.
- Editing the parent theme. The next update overwrites the change. Use a child theme.
- Trusting the editor preview. Check the live source; sanitising or caching can change it.
- Leaving plugin defaults unchecked. A default logo, phone number or author name can be wrong for your business.
- Forgetting the cache. A page cached before the change still shows the old markup.
Frequently asked questions
Do I need a plugin to add schema markup to WordPress?
No. A plugin is often the easiest option, but you can add JSON-LD with a Custom HTML block or a few lines of PHP in a child theme. Choose based on how many pages need it and who will maintain it.
Does WordPress add schema markup by default?
Possibly. Themes and plugins often print JSON-LD, so check your own page source instead of assuming either way. That is why the audit comes first.
Where should the JSON-LD go on the page?
Google says JSON-LD can sit in the head or the body. Hooking wp_head puts it in the head, which suits site-wide and template-driven output.
Will schema markup improve my rankings?
Google presents structured data as a way to understand a page and possibly show richer results, and does not guarantee that. Do not expect a ranking change from markup alone.
Can I use Microdata instead of JSON-LD in WordPress?
Google accepts JSON-LD, Microdata and RDFa and recommends JSON-LD. See JSON-LD vs Microdata for how they differ.
How do I find which plugin is printing a block?
Deactivate one suspect plugin at a time on a staging copy and reload the page source; the block that disappears came from it. Never do this experiment on a busy live site.
Related guides
- WordPress SEO Checklist: Settings to Check Before and After Launch
- JSON-LD Validation: Test and Debug Structured Data Step by Step
- How do I use the Rich Results Test?
- JSON-LD vs Microdata: which should I use?
- Video Schema Markup: VideoObject Example, Required Fields and Testing
Official references
- https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data
- https://developers.google.com/search/docs/appearance/structured-data/sd-policies
- https://developer.wordpress.org/reference/hooks/wp_head/
- https://developer.wordpress.org/themes/advanced-topics/child-themes/
- https://developer.wordpress.org/reference/functions/wp_json_encode/
- https://wordpress.org/documentation/article/custom-html/
Next step: Try the free website sample to see how your own site does on a few of these checks. This guide provides technical information, not a guarantee of rankings, traffic or index inclusion.