You can have a perfectly optimized Google Business Profile, a website that loads in under a second, and citations clean enough to pass an audit. And still get outranked by a competitor whose site looks like it was built in 2014. I've watched this happen to a client — a dental practice in a mid-sized city — and the fix that finally moved the needle wasn't backlinks or content. It was eleven lines of JSON-LD that told Google, in unambiguous machine language, exactly which business was at which address, open when, and reviewed by whom.
Schema markup for local SEO is the part of the work most people skip. Not because it's hard — the code itself is genuinely simple — but because it's invisible. You ship it, nothing visibly changes, and you move on. That's a mistake I made for the better part of a year.
Key Takeaways
- LocalBusiness schema is not a ranking factor on its own, but it feeds entity understanding, rich results, and — increasingly in 2026 — how AI answer engines describe your business.
- The properties that actually matter: name, address, telephone, geo, openingHoursSpecification, url, and sameAs. Everything else is refinement.
- JSON-LD is the format to use. Microdata works; it's just messier to maintain.
- NAP consistency between your schema and your Google Business Profile is where most silent failures live.
- Validate with Google's Rich Results Test before you ship, then check Search Console's enhancement reports two weeks later.
- Multi-location businesses need one schema block per location, each with its own
@id.
Why local business schema still wins in 2026
Google has never confirmed structured data as a ranking signal, and I'm not going to pretend otherwise. What it does confirm is that structured data helps understanding. For a local business, that understanding has three concrete payoffs, and the third one is new enough that most sites haven't caught up.
Three payoffs worth understanding
First, rich results. Star ratings in the SERP, FAQ dropdowns, breadcrumb trails, opening hours shown directly in the listing. These don't change your position, but they change your click-through rate. On a client's homepage, adding review markup pushed CTR from around 2.8% to 3.9% over two months — same position, more clicks.
Second, entity clarity. Google builds a knowledge graph of businesses. Schema is one of the cleanest ways to tell it "this URL, this physical location, this phone number, and the Facebook page over there are all the same entity." When those signals contradict each other, Google picks the one it trusts most — and it may not pick yours.
Third, and this is the part I underestimated: AI assistants and answer engines lean on structured data when they generate a local recommendation. If someone asks an AI tool for "a good plumber near me," the systems pulling from structured sources have something to read. The ones scraping unstructured HTML have to guess. In practice, sites with clean LocalBusiness markup get cited more reliably.
The mistake most people make
They copy a LocalBusiness snippet from a generator, paste it in a header plugin, and consider the job done. Then the phone number in the schema differs from the one on the Google Business Profile by a single digit, or the address uses "St." where the profile uses "Street." Result: nothing breaks visibly, but the entity signal gets muddy.
I did exactly this on my own side project for about eight months. When I finally audited it, two of the three discrepancies traced back to a theme update that had silently overwritten part of my markup. Which brings up the obvious problem: schema isn't a one-time install.
The JSON-LD block that actually works
Skip the debate about Microdata versus RDFa. Use JSON-LD — it lives in a <script> tag, doesn't touch your visible HTML, and survives theme changes better than inline attributes. Here's a working LocalBusiness example with the properties that matter:
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"@id": "https://example.com/#business",
"name": "Riverside Dental Studio",
"image": "https://example.com/images/storefront.jpg",
"url": "https://example.com",
"telephone": "+1-555-0147",
"priceRange": "$$",
"address": {
"@type": "PostalAddress",
"streetAddress": "1240 Riverside Ave, Suite 3",
"addressLocality": "Portland",
"addressRegion": "OR",
"postalCode": "97209",
"addressCountry": "US"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": 45.5231,
"longitude": -122.6765
},
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday"],
"opens": "08:00",
"closes": "17:00"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": "Friday",
"opens": "08:00",
"closes": "14:00"
}
],
"sameAs": [
"https://www.facebook.com/riversidedentalpdx",
"https://www.instagram.com/riversidedentalpdx"
]
} What to include and what to skip
Required by Google for LocalBusiness eligibility: name, address, and either telephone or priceRange. In practice, add url, geo, openingHoursSpecification, and sameAs because they resolve ambiguity. Skip aggregateRating unless you have genuinely collected reviews you can display on the page — marking up ratings that aren't visible violates Google's guidelines and can cost you the rich result entirely.
The @id property is the one that gets overlooked. It gives your business a permanent identifier in the graph, so other schema blocks on your site (services, articles, FAQ) can reference "provider": {"@id": "https://example.com/#business"} instead of re-declaring the whole business. I've seen this single change reduce duplicate-entity confusion in Search Console's knowledge panel for a service business.
Use a subtype, not just LocalBusiness
Generic LocalBusiness is acceptable. But Dentist, Restaurant, LegalService, Plumber, AutoRepair — these are all real Schema.org subtypes and they're more precise. Google's documentation maps subtypes to specific rich-result behaviour. If your type exists, use it. It costs nothing.
How to add local business schema to WordPress
Three routes, and I've used all three. Ranked by how much I'd recommend them:
- Yoast or Rank Math — built-in LocalBusiness settings in the free or low-cost tiers. Fastest path if you already use one of them. The limitation: less control over
@idand nested references. - A dedicated schema plugin — more granular, per-page control, and handles multi-location better than the general SEO plugins in my experience.
- Manual injection via a child theme's
functions.phpor a code snippets plugin. Full control, zero plugin overhead, and it's what I use on sites where I need exact output. Downside: you own the maintenance.
The trap with the plugin route is duplication. If Yoast is outputting one LocalBusiness block and a separate plugin is outputting another, you've created two competing entity declarations on the same page. I've seen this on probably a third of the WordPress sites I've audited. Check the rendered source before assuming.
Verify the output, always
View the page source (not the DevTools inspector — some schema is injected after page load and won't appear in the raw HTML for crawlers). Search for application/ld+json. Count the blocks. If you see more than one LocalBusiness declaration, you have a problem to fix before anything else.
Validation and diagnosis: where the real work happens
Shipping the markup is the easy half. Confirming that Google actually accepted it is the half people skip, and it's where the failures hide.
The validation stack I use
- Schema Markup Validator (validator.schema.org) — checks the markup against Schema.org vocabulary, independent of Google's rules. Good for catching typos and malformed properties.
- Rich Results Test — checks against Google's specific eligibility criteria. This is the one that matters for rich-result eligibility. If it says "eligible," you're in business.
- Search Console enhancement reports — the ground truth. Two to four weeks after shipping, check the "Enhancements" section for errors and warnings on your LocalBusiness items. This reflects what Google actually crawled and parsed, not what you think you shipped.
My routine: validate before deploy, then set a calendar reminder for three weeks out to check Search Console. That reminder has caught more issues than any other habit I've built around technical SEO.
The errors that turn up most
Missing streetAddress inside PostalAddress. Opening hours written as free text ("Mon–Fri 8-5") instead of using the structured specification. image pointing to a URL that 404s. And the classic: schema on the homepage only, with no corresponding markup on the location-specific service pages — which matters enormously for multi-location businesses.
Multi-location schema without creating a mess
Two branches, one domain. This is the scenario where I see the most damage. The instinct is to put one LocalBusiness block on the homepage listing both addresses. That's wrong — a single entity can't have two addresses in a way that's meaningful to a crawler.
Instead: one schema block per location page, each with a distinct @id (e.g. https://example.com/portland/#business and https://example.com/seattle/#business). Each gets its own name, address, phone, hours, and geo. The Google Business Profile for each location aligns to its matching page. And where it applies, don't forget areaServed — it's how you tell search engines which geographic zones each location covers, which matters for service-area businesses that don't publish a storefront address.
If your parent organisation warrants one, a separate Organization block on the homepage with subOrganization links to each location is the cleanest structure. It takes an afternoon to set up and saves months of confusing entity signals.
| Scenario | Schema approach | Common failure |
|---|---|---|
| Single location, one page | One LocalBusiness block on homepage + contact page | Duplicated blocks from conflicting plugins |
| Multi-location, dedicated pages | One block per location page, distinct @id | Copy-pasted blocks with unchanged address |
| Service-area business (no storefront) | LocalBusiness with areaServed, no street address | Fabricated address to satisfy the validator |
| Franchise | Organization on parent site, LocalBusiness per franchisee | Franchisee schema declaring the parent's NAP |
NAP consistency is the hidden variable
Your schema, your Google Business Profile, your Bing Places listing, your citations — all of them should agree on the same business name, address format, and phone number. Down to the abbreviation. If one says "Suite 3" and another says "Ste. 3," you've introduced a mismatch that forces Google to adjudicate. It usually adjudicates correctly. Usually.
The audit I run: pull the NAP from the schema block, from the GBP, and from your three biggest citations. Put them side by side. Investigate every variance. It's tedious, unglamorous work and it's caught real problems for me — a client whose phone number in schema was the old office line, retired two years earlier.
The part that keeps me coming back to schema
Structured data is one of the few remaining levers in local SEO where the work is deterministic. You write the markup correctly, you validate it, you keep it consistent — and the result doesn't depend on an algorithm update or a competitor's budget. It's plumbing. Invisible when it works, catastrophic when it doesn't, and almost nobody bothers to inspect it.
Which is exactly why it's worth your afternoon. The businesses that treat their schema as infrastructure rather than a checkbox will keep the advantage — and as AI systems take over more of the local discovery layer, that gap widens.