Local Business Schema Markup: The Guide That Goes Past Copy-Paste

local business schema markup

Almost every guide to local business schema markup hands you a JSON-LD block, tells you to paste it, and implies rankings will follow. They won’t — not from the markup alone. Schema doesn’t rank you. It makes you legible: it tells Google exactly which entity you are, where you operate, and how you connect to the rest of the web, so the ranking systems have something unambiguous to work with. Get that legibility right and you unlock rich results, cleaner knowledge-panel data, and citations in AI answers. Get it wrong — a generic type, a mismatched address, a self-serving review block — and you either get nothing or you get an error that quietly suppresses the feature you wanted.

What Local Business Schema Markup Actually Does

Structured data is a translation layer. Your page is HTML written for humans; local business schema markup is the same facts written for a parser, in a vocabulary (schema.org) that Google, Bing, and increasingly the LLMs behind AI answers already understand. When you declare a LocalBusiness with a name, address, phone, and geo-coordinates, you’re not asking for a ranking boost — you’re removing ambiguity. Google no longer has to guess whether “Riverside Plumbing” on your page is the same entity as the one in Maps, the one with 214 reviews, and the one three competitors mention by name.

That disambiguation is the mechanism. It feeds entity resolution — the process of Google deciding you are one specific real-world thing with one identity — which in turn feeds knowledge panels, the local pack’s confidence in your data, and whether an AI Overview cites you correctly instead of confusing you with a similarly named business two towns over. None of that is a keyword ranking. All of it affects how often the right searcher reaches the right business.

Pick the Most Specific Subtype — Generic Costs You

The single most common mistake is stopping at @type: "LocalBusiness". Schema.org publishes a deep hierarchy beneath it — Dentist, Plumber, Attorney, Restaurant, HairSalon, AutoRepair, and dozens more. Every specific subtype is a LocalBusiness (it inherits all the same properties), so you lose nothing by being precise and you gain a clearer entity signal plus, for some verticals, access to type-specific properties like menu, servesCuisine, or priceRange.

The rule: descend the hierarchy as far as an exact match exists, then stop. A family dental practice is a Dentist, not a generic MedicalBusiness. A firm that genuinely spans categories — say a clinic that’s both dental and orthodontic — can declare an array: "@type": ["Dentist", "MedicalClinic"]. What you should not do is invent a subtype that doesn’t exist in the vocabulary, or reach for a broader type “to be safe.” Broad is not safe; it’s vague, and vague is exactly what schema is supposed to eliminate.

Required Properties vs the Ones That Move the Needle

Google’s documentation lists a short set of required fields — and a longer set that does the actual work. Meeting the minimum passes validation; it doesn’t build a strong entity.

  • Required: name, a complete address (as a nested Postaladdress with street, locality, region, postal code, country), and telephone.
  • High-value: geo (latitude/longitude — pull the exact pin from Google Maps, not an approximation), openingHoursSpecification, url, image, and sameAs pointing to your verified profiles.
  • Situational: priceRange, areaServed, hasMap, and vertical-specific properties.

sameAs deserves special attention because it’s the cheapest high-leverage field there is. It’s an array of authoritative URLs that are unambiguously you — your Facebook page, LinkedIn, Yelp, Instagram, and ideally your Wikidata or Google Business Profile share link. Each one is a corroborating vote for your identity. This is the difference between Google inferring who you are and Google being told, with receipts.

The @id Entity Graph: The Part Most Guides Skip

Here’s the information gain almost no copy-paste snippet gives you. Modern structured data isn’t a lone island of markup — it’s a small graph of connected nodes, and the connector is @id. You assign each entity a stable, canonical @id (a URL fragment like https://example.com/#localbusiness), and then reference it from other nodes so Google reads them as one linked identity rather than three unrelated blobs.

Concretely: your Organization, your WebSite, and your LocalBusiness should reference each other. The LocalBusiness @id is the anchor; your Organization node points to it or shares its identity; your WebSite node’s publisher points back to the Organization. Done well, this produces a single coherent entity graph instead of a scatter of properties, and it’s what lets a homepage, a contact page, and a location page all reinforce one identity instead of competing to define it. If you run one site, use one canonical @id for the business and reference it everywhere rather than redeclaring the whole block on every page.

Storefront vs Service-Area: address Is Not Always the Answer

A plumber who drives to customers and a plumber with a walk-in showroom need different markup, and conflating them is a quiet, common error. If you serve customers at their location and have no public storefront, publishing a full street address can misrepresent you — and it mirrors the “hide my address” choice you already make in Google Business Profile. For those service-area businesses, lean on areaServed (a city, region, or list of postal codes) and consider serviceArea with a GeoCircle radius, rather than pinning a precise storefront address you don’t operate from. The principle is honesty of representation: the markup should describe the business that actually exists, because the moment it contradicts your Google Business Profile, Google trusts the profile and discounts your markup.

A Worked Minimal Example

Below is a correct, deployable block for a single-location plumber. Note the 24-hour times, the international phone format, the exact geo pin, and — critically — the @id that other nodes on the site can reference.

{
  "@context": "https://schema.org",
  "@type": "Plumber",
  "@id": "https://riversideplumbing.example/#business",
  "name": "Riverside Plumbing",
  "url": "https://riversideplumbing.example",
  "telephone": "+1-503-555-0142",
  "priceRange": "$$",
  "image": "https://riversideplumbing.example/storefront.jpg",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "418 River Road",
    "addressLocality": "Portland",
    "addressRegion": "OR",
    "postalCode": "97201",
    "addressCountry": "US"
  },
  "geo": {
    "@type": "GeoCoordinates",
    "latitude": 45.5202,
    "longitude": -122.6742
  },
  "openingHoursSpecification": [{
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
    "opens": "08:00",
    "closes": "17:00"
  }],
  "sameAs": [
    "https://www.facebook.com/riversideplumbing",
    "https://www.yelp.com/biz/riverside-plumbing-portland"
  ]
}

That’s the whole minimum-correct footprint: every required field, the high-value ones that build identity, and nothing invented. You extend it with areaServed, additional locations, or vertical properties as your real business justifies — never to pad the block.

Formatting Rules That Silently Break Your Markup

Most “my schema isn’t working” tickets trace to formatting, not concept. The parser is strict where a human would forgive.

  • Hours use 24-hour time. "opens": "17:00", never "5:00 PM". Split-shift or per-day hours need separate OpeningHoursSpecification objects, not a comma-jammed string.
  • Phone numbers should be international format (+1-503-555-0142) and match your Google Business Profile character-for-character.
  • Address components go in the nested PostalAddress, not one flattened string. A single "address": "418 River Road, Portland OR" is technically parseable but weaker and error-prone.
  • Prefer JSON-LD over microdata. It lives in one <script> tag, decoupled from your visible HTML, so a template change doesn’t shatter your markup the way inline microdata attributes do.

The Reviews and Ratings Trap

This is the honest caveat that gets sites into trouble. It is tempting to add aggregateRating and self-hosted review markup to your own LocalBusiness so star ratings show in search. Google’s policy is explicit: you cannot mark up reviews that you write about your own business, or ratings you collect and host about yourself, to earn review rich results — that’s self-serving content, and it can trigger a structured-data manual action if abused. Legitimate aggregate ratings come from independent third-party platforms, and even then Google displays them selectively for local entities, largely preferring the review signal it already gathers through Google Business Profile. The durable move is to earn real reviews on your profile and third-party sites, not to manufacture stars in your own JSON-LD.

Consistency With Google Business Profile Is the Whole Game

Your local business schema markup does not exist in isolation. Google cross-checks it against your Google Business Profile, your citations across directories, and mentions on other sites. When your NAP — name, address, phone — matches everywhere, the markup reinforces a confident entity. When it contradicts — a “Suite 4” here, an old phone number there — you’re not adding signal, you’re adding noise, and Google resolves the conflict by trusting the source it controls (the profile) and discounting yours. Treat the profile as the source of truth and make every field in your schema match it exactly, including abbreviations and formatting.

Validate the Right Way: Invalid vs Warnings

Use Google’s Rich Results Test and the Schema Markup Validator, not the retired Structured Data Testing Tool. Read the output carefully, because the two failure classes mean very different things. An error (missing required field, malformed value) means the rich result is disqualified — fix it before anything else. A warning (a recommended field you omitted, like priceRange) means it’ll still work but you’re leaving signal on the table. After deployment, watch Search Console’s enhancement reports over the following weeks — that’s where you catch markup that validated in a testing sandbox but failed on the live, JavaScript-rendered page. Server-render or statically embed the JSON-LD; schema injected late by client-side scripts is the classic “passes the test, never appears in Search Console” bug.

Multi-Location and Doing This at Scale

One location is a snippet. Fifty locations is a content operation. Each location gets its own page with its own LocalBusiness block and its own unique @id — never one shared block reused across all of them. Tie each location back to a single parent Organization via parentOrganization or branchOf so Google reads a chain, not fifty unrelated businesses. At that volume, hand-editing JSON is where errors breed, so templating and validation-in-the-loop matter more than clever markup. This is the same discipline behind SEO Rocket’s real-crawler site audit — it renders pages the way Google does and flags structured-data and consistency issues across a whole site, which is how you catch the one location page where a template variable didn’t populate the postal code before it costs you a rich result.

How Schema Fits a Wider Local SEO Program

Markup is one input, not a strategy. The pages carrying your local business schema markup still have to earn their rankings the ordinary way: target real local intent, answer it more completely than the weakest competitor on page one, and back it with legitimate local citations and links. That’s where the rest of a proper toolkit earns its place — AI keyword research on real Ahrefs data to find the city-and-service queries worth a page, competitor gap analysis to see which location or service pages rivals rank for that you don’t, and rank tracking plus AI-visibility monitoring to confirm the schema is actually feeding richer results and citations over time. SEO Rocket bundles that workflow — validation-gated AI writing, gap analysis, real-crawler audits, and a client dashboard — for around $50/month with a free tier, drawing on a playbook proven across 1,000,000+ ranking pages. The schema makes you legible; the program makes you rank.

Frequently Asked Questions

Does local business schema markup directly improve my rankings?

No — not as a direct ranking factor. It improves entity disambiguation and unlocks rich results, richer knowledge-panel data, and cleaner citations in AI answers. Those raise visibility and click-through, which can lift traffic, but the markup itself doesn’t move you up the results for a query. Content, relevance, and authority still do that.

Should I put the same schema block on every page of my site?

For a single-location business, declare one canonical LocalBusiness with a stable @id and reference that identity from other pages rather than duplicating the full block everywhere. For multiple locations, each location page gets its own unique block and @id, all tied to one parent Organization.

Can I add star ratings to my own business markup?

Not from reviews you write or host about yourself — Google treats self-serving aggregateRating and review markup as a policy violation that can earn a manual action. Legitimate ratings come from independent third-party platforms, and for local businesses Google mostly relies on the reviews already on your Google Business Profile.

JSON-LD or microdata — does it matter?

Use JSON-LD. Google explicitly prefers it, and because it lives in a single script tag decoupled from your visible HTML, it survives template changes that would break inline microdata. Just make sure it’s server-rendered or statically embedded so crawlers see it without executing client-side JavaScript.

Questions? Chat with us