Guide

How to Add Schema Markup in WordPress

With and without a plugin.

By Antti Rydman12 min read

I built a schema audit tool. Before that, I spent an unreasonable amount of time adding structured data to a 650-page supplier directory by hand, until I came to my senses and automated it.

Both experiences taught me the same thing. Most WordPress sites already have more schema markup than their owners realise, and most guides about adding schema markup to WordPress skip the parts that actually matter.

They tell you to install Yoast. They give you a JSON-LD snippet to paste somewhere. Then they stop, right at the point where the real questions start. What do I already have? What am I missing? Did any of this work?

This guide covers the plugin route, the manual route, and what to do when you have too many pages to handle either by hand. It also covers how to check that what you added actually shows up, which is the step nearly everyone skips.

The quick answer

Most WordPress sites already output basic schema through their SEO plugin. If you're running Yoast or Rank Math, you probably have Article, Organization, WebSite and BreadcrumbList markup right now without having done anything.

For most sites, configuring that plugin properly is enough. If you need business-specific schema (LocalBusiness with opening hours, Product with prices, FAQ, a restaurant Menu) the plugin won't do it, and you'll need to add JSON-LD manually. If you have hundreds of pages, you generate it from your page data instead of writing it.

Then you check whether it worked. Not on one page. On all of them.

What schema markup actually does

Schema markup is a label you attach to your content so that machines understand what it is.

Without it, a search engine reads your restaurant's opening hours as a piece of text that happens to contain numbers. With it, the same text is explicitly tagged as opening hours. Google can then show "Open now" in the search results, and an AI assistant can answer "is this place open on Sunday?" without guessing.

That's the whole idea. You're not writing anything new. You're describing what's already on the page in a format that isn't ambiguous.

The payoff shows up in two places. In Google, it makes you eligible for rich results: the star ratings, the FAQ dropdowns, the prices and availability that take up more space in the listing and get clicked more. And increasingly in AI search, where structured data helps ChatGPT, Perplexity and Google's AI Overviews understand and cite you correctly rather than paraphrasing you badly.

Both Google and Schema.org document all of this in detail. Their documentation is thorough and almost entirely written for developers, which is why guides like this one exist.

First, find out what you already have

Before you add anything, look at what's there.

This matters more than it sounds. WordPress SEO plugins output schema automatically, and most site owners have no idea. If you paste in an Organization block that Yoast is already generating, you now have two, and search engines have to work out which one you meant.

Here's what that looks like in practice. This is a real audit of an article page on a WordPress site running Yoast:

Image: Schema audit of a WordPress site showing 13 detected schema types, of which 7 are primary
Schema audit of a WordPress site showing 13 detected schema types, of which 7 are primary

Thirteen schema types. The site owner added exactly none of them.

But look closer, because this is where most tools mislead you. Of those thirteen, only seven are meaningful: Article, BreadcrumbList, ImageObject, Organization, Person, WebPage, WebSite. The other six (EntryPoint, ListItem, PropertyValueSpecification, ReadAction, CommentAction, SearchAction) are structural. They're the plumbing that sits inside the schemas you care about. A SearchAction lives inside WebSite. A ListItem lives inside a BreadcrumbList. Yoast generates them automatically as part of doing its job properly.

I mention this because most schema checkers hand you a count and let you draw your own conclusions. "Thirteen schema types" sounds like a well-marked-up page. "Seven meaningful types, six structural" is the honest version, and it tells you something useful. This page is doing fine on the basics. If anything is missing, it'll be the business-specific markup Yoast was never going to add for you.

So: audit first. Then add what's actually missing.

The three ways to add schema markup in WordPress

MethodEffortControlBest for
SEO pluginLowLimited, basics onlyMost sites. Article, Organization, Breadcrumb.
Manual JSON-LDMediumFullThe types your plugin won't do: LocalBusiness, Product, FAQ, Menu.
Dynamic generationHigh, needs PHPFull, and it scalesHundreds of pages. Directories, catalogues, multi-location businesses.

Most people need the first. Some need the second. If you need the third, you probably already know it.

Method 1: Use your SEO plugin

For the majority of WordPress sites, this is the right answer and you can stop reading after this section.

Yoast SEO outputs a connected graph of schema by default: Article or WebPage depending on the content type, plus Organization, WebSite, BreadcrumbList, and Person for the author. It's competent and it's already running.

Rank Math does much the same, with a slightly more generous free tier for choosing schema types per post. You can set a page to Product, Recipe, FAQ and a handful of others from a dropdown.

Schema Pro and similar dedicated plugins go further, giving you more types and more control, at the cost of another plugin to maintain.

Whichever you're using, go and configure it properly, because the defaults are generic:

  • Organization vs Person. In Yoast this lives under Settings → Site representation. Get it wrong and every page on your site tells Google you're the wrong kind of entity.
  • Your logo. Feeds the Organization markup. Missing logos are one of the most common gaps I see.
  • Social profiles. These populate the sameAs property, which is how Google connects your site to your other identities across the web.
  • Author profiles. If you write under a personal name, fill in the author bio and social links. This feeds Person schema.

Now the honest part. Plugins handle the structural, site-wide markup well. What they don't do is describe your business. Yoast will never tell Google your opening hours, your price range, your menu, or that you have 47 five-star reviews. It doesn't know any of that. If you want that markup, and if you're a local business you very much do, you're adding it yourself.

Method 2: Add schema markup to WordPress without a plugin

You can add schema by hand. You don't need a plugin to do it, and sometimes a plugin can't do what you need anyway.

The format is JSON-LD, a block of structured data that sits in a script tag, separate from your page content:

html
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "LocalBusiness",
  "name": "Casa Vela",
  "image": "https://example.com/restaurant.jpg",
  "telephone": "+34 931 234 567",
  "priceRange": "€€",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "Carrer d'Exemple 1",
    "addressLocality": "Barcelona",
    "postalCode": "08001",
    "addressCountry": "ES"
  },
  "geo": {
    "@type": "GeoCoordinates",
    "latitude": 41.3851,
    "longitude": 2.1734
  },
  "openingHoursSpecification": [{
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
    "opens": "13:00",
    "closes": "23:00"
  }],
  "url": "https://example.com"
}
</script>

Reading that top to bottom: you're declaring that this is a LocalBusiness, giving it a name and a photo, a phone number, a rough price bracket, a proper postal address broken into its parts, exact coordinates so it maps correctly, and the hours it's open. Nothing here is invented. It's all information already sitting on the page in human-readable form. You're just restating it unambiguously.

JSON-LD is Google's recommended format and it's the one to use. The alternative, microdata, means weaving itemprop attributes through your actual HTML, which is fragile and miserable to maintain. JSON-LD sits in one block. You can read it, edit it, and delete it without touching your page.

Where to put it. Three options, in ascending order of how much you should trust yourself:

  1. A code snippets plugin. WPCode or similar. You paste the JSON-LD in, tell it to load in the header, done. No theme files touched, survives updates, easy to remove. This is what I'd recommend to most people.
  2. Your theme's functions.php, via the wp_head hook. Fine if you're comfortable with PHP, and it keeps things plugin-free. But if you edit the parent theme's functions.php directly, your changes vanish on the next theme update. This has happened to a lot of people and it will happen to you.
  3. A child theme. Same approach, but your changes survive updates. More setup, more safety.
One caution that matters. Schema has to describe what's actually on the page. If you mark up review ratings that don't exist, or opening hours that contradict your contact page, or a price you don't charge, you're not being clever. You're risking a manual penalty. Google checks. The markup describes reality. It doesn't invent it.

Method 3: Generate schema markup dynamically

At some point, hand-writing schema stops being viable.

I hit this wall on a supplier directory with roughly 650 listings. Every listing needed LocalBusiness markup: name, address, phone, website, category. Writing 650 JSON-LD blocks by hand was obviously absurd. So was leaving 650 pages with no structured data at all.

The answer is to stop writing schema and start generating it. You write one template. It reads each page's actual data, the title, the address field, the phone number, whatever you've already stored, and outputs the correct JSON-LD for that page. Six hundred and fifty pages, one piece of code.

In WordPress the data is already there. Custom fields, ACF fields, post meta, taxonomy terms, whatever you're using to store the listing details. A function hooked into wp_head reads those fields for the current page and prints the markup:

php
add_action('wp_head', function() {
    if (!is_singular('listing')) return;

    $schema = [
        '@context' => 'https://schema.org',
        '@type'    => 'LocalBusiness',
        'name'     => get_the_title(),
        'address'  => [
            '@type'           => 'PostalAddress',
            'streetAddress'   => get_field('street_address'),
            'addressLocality' => get_field('city'),
            'addressRegion'   => get_field('state'),
        ],
        'telephone' => get_field('phone'),
        'url'       => get_field('website'),
    ];

    echo '<script type="application/ld+json">'
        . wp_json_encode($schema)
        . '</script>';
});

That's the shape of it. Not a complete implementation, but you can see the logic. Check what kind of page this is, pull the real data, build the structure, print it.

On the directory I went a step further. The category pages listing multiple suppliers got a CollectionPage containing an ItemList, where each item references the individual supplier's LocalBusiness entity by @id rather than repeating all its details. That way the relationship between "here is a list of lighting suppliers" and "here is Acme Lighting, a local business" is explicit, and nothing is duplicated.

Now the honest caveat. This is developer territory. If you're a marketing manager who found this guide because someone told you your site needs schema, Method 3 is not your afternoon project. Either you have a developer, or you use a plugin that generates schema from your custom fields, or you get someone to set it up once and then it runs forever. There's no shame in the third option. It's what I'd tell my own clients.

Check whether it actually worked

Adding schema is not the last step. Verifying it rendered is.

I say this because a surprising amount of schema markup doesn't work. It's malformed, or it's in the wrong place, or the plugin overwrote it, or it's sitting in a JavaScript file that never loads. You will not know unless you look.

Google's Rich Results Test takes a single URL and tells you which rich results it qualifies for. This is the one to use when you've just added something and want to know if Google can see it.

The Schema.org validator checks your syntax against the spec. Less about search results, more about whether it's even valid.

Both are good. Both have the same limitation: they check one page at a time.

That's fine when you've just added LocalBusiness markup to your contact page. It falls apart when you have forty blog posts, or two hundred products, or six hundred and fifty directory listings, and you need to know whether the schema is there on all of them. Nobody is pasting six hundred URLs into a form.

This is the gap I built Sniff My Schema to fill. It crawls your whole site and tells you what schema each page has and what's missing for that type of page. Free for the first 25 pages, which covers most small sites entirely.

Image: Whole-site schema audit report showing missing schema across WordPress pages
Whole-site schema audit report showing missing schema across WordPress pages

There's a second reason a whole-site check matters, and it's less obvious. Some schema only appears after JavaScript runs. If your theme or a plugin injects structured data client-side, tools that read the raw HTML will miss it entirely, and so, potentially, will some AI crawlers. You want to know that. A tool that renders the page the way a browser does will see it. One that doesn't, won't.

Mistakes I see constantly

  • Duplicating what the plugin already does. Someone reads a guide, pastes in an Organization block, and now Yoast's Organization and their Organization are both on the page. Audit first.
  • Marking up things that aren't visible. Schema describes the page. If the FAQ markup lists ten questions and the page shows three, that's a problem.
  • Review schema without reviews. The single fastest way to get a manual penalty. Star ratings are the most tempting rich result and the most policed.
  • Assuming more types is better. Thirteen schema types on a page isn't a score. Six of them might be structural plumbing. What matters is whether the right types are there for what that page actually is.
  • Adding it and never checking. Covered above, but it bears repeating. It's the most common failure and the easiest to fix.

Frequently asked questions

Do I need a plugin to add schema markup in WordPress?

No. You can add schema manually by inserting JSON-LD into your page's HTML. For most sites, though, an SEO plugin like Yoast or Rank Math handles the basics automatically and is the simpler route.

Does Yoast add schema markup automatically?

Yes. Yoast outputs Article, WebPage, Organization, WebSite and BreadcrumbList schema by default. It does not cover business-specific types like LocalBusiness details, Product, Menu or FAQ. Those must be added separately.

Will schema markup improve my Google rankings?

Schema is not a direct ranking factor. It makes your pages eligible for rich results such as star ratings, FAQs and prices, which can significantly increase click-through rate. It also helps AI search engines understand and cite your content correctly.

What format should schema markup be in?

JSON-LD. It's Google's recommended format and the easiest to maintain, because it sits in a single script tag rather than being woven through your HTML.

Where do I put schema markup in WordPress?

Inside a <script type="application/ld+json"> tag in the page's head or body. In practice: a code snippets plugin, a function hooked into wp_head in your theme, or your SEO plugin's own settings.

How do I know if my schema markup is working?

Test individual pages with Google's Rich Results Test. To check your whole site at once, and to catch schema that only appears after JavaScript loads, use a site-wide schema audit.

How do I add schema to hundreds of pages?

Generate it dynamically. Write a template that pulls each page's data (title, address, price) and outputs the schema automatically, rather than writing it page by page. This needs PHP, or a plugin that builds schema from your custom fields.

Where to start

Check what you have. Configure your SEO plugin properly. Add JSON-LD by hand for the business-specific markup the plugin won't do. If you have hundreds of pages, generate it instead of writing it. Then verify it worked, across your whole site, not just the one page you happened to test.

Most of this is more straightforward than it looks. The part people get stuck on isn't the syntax. It's not knowing what they already have, or whether what they added is doing anything.

Antti Rydman
About the author
Antti Rydman

Antti Rydman built SniffMySchema, a whole-site schema and AI-visibility audit tool, after nearly losing his mind adding structured data by hand to a 650-page supplier directory. He now spends his days looking at other people's structured data, which is not the retirement he imagined.