Website & schema markup · Intermediate
LocalBusiness Schema Markup: The Complete Guide With JSON-LD Examples
What LocalBusiness schema markup does for local SEO (and what it does not), choosing the type and properties, hours and review rules, single and multi-location JSON-LD examples, and validation.
Free tool for this guide
LocalBusiness Schema Markup Generator
Generate valid LocalBusiness JSON-LD — prefilled from your Google profile.
Your Google Business Profile tells Google who you are. Your website should say the same thing in a form a machine can read without guessing, which is what LocalBusiness schema markup does. This guide covers what the markup can and cannot do, how to choose the type and properties, working JSON-LD for one location and for a two-clinic practice, the hours and review rules that trip people up, and how to validate. Our free LocalBusiness Schema Markup Generator produces the code; this guide is the judgment around it.
What structured data is, and what Google does with it
Structured data is a block of labeled facts embedded in a page: this is a Dentist, its name is X, its phone is Y, it opens at 08:00. It uses the schema.org vocabulary search engines agreed to read, so Google does not have to infer those facts from your footer.
Google’s documentation for LocalBusiness structured data lists what the markup can feed: details in a knowledge panel, hours, departments and, for sites that review other businesses, ratings. Read that with a cool head: for a business marking up its own site the visible change is usually nothing dramatic; the knowledge panel keeps drawing from the Google Business Profile.
Why bother, then. Disambiguation: Google matches profile to website partly by comparing name, address and phone, and a machine-readable statement removes doubt, especially for common names and shared buildings. Consistency: Google’s local ranking guidance says information about your business from across the web feeds prominence, and the markup is one more place where your details appear exactly as on the profile. AI assistants read pages too, and unambiguous data helps them quote your phone and hours correctly.
What it is not: a ranking switch. Google has never listed it as a ranking factor, and in our experience adding it to a site with clean details changes nothing measurable in the local pack. The general structured data guidelines add that a valid block never guarantees a rich result. Fix the profile and reviews first; add schema because it is cheap and correct.
JSON-LD, not microdata
Schema.org data can be written as JSON-LD (one script block), microdata (attributes on your HTML tags) or RDFa. Google recommends JSON-LD and so do we: it sits in one place, a theme change cannot break it, a form can generate it, and it is what nearly every plugin produces, so conflicts are easier to spot. Microdata ties each fact to an HTML element, and a redesign quietly removes half of it. If you inherit microdata, replace it rather than adding JSON-LD alongside it. Our guide on adding schema markup to your website covers where the block goes on each platform.
Choosing the right type
LocalBusiness is a parent type on schema.org with dozens of subtypes: Dentist, Plumber, Restaurant, HairSalon, AutoRepair, Physiotherapy, VeterinaryCare and so on. Google asks for the most specific subtype that fits. The type is a claim about what you are, so choose it the way you chose your primary profile category.
Our rule: use the subtype that matches your primary Google Business Profile category. If none exists (many trades have none), use LocalBusiness itself and say what you do in description. If you genuinely are two things, Google accepts an array such as "@type": ["Electrician", "Plumber"] and says additionalType is not supported. Avoid plain Organization for a location: it says nothing about place.
Two traps. ProfessionalService is deprecated on schema.org and tells Google almost nothing. MedicalBusiness, Store and FoodEstablishment are parents with more specific children (Dentist, HardwareStore, Bakery); go one level deeper when a child fits. The generator lists about 45 subtypes and preselects the closest match to your profile’s primary type; check it anyway.
Required and recommended properties
Google’s page separates required properties from recommended ones. At the time of writing only name and address are required; everything else is recommended, with Google’s note that more complete markup makes a higher quality result. Earlier versions also required @id; we still set one, because it is what lets a multi-location graph reference each node. Since LocalBusiness is a subtype of Organization, Google also points you at the Organization properties (logo, sameAs, url). The list moves; let the Rich Results Test tell you what it considers missing today.
| Property | Status | What to put in it |
|---|---|---|
name |
Required | The real-world name, exactly as on the profile |
address |
Required | PostalAddress: street, locality, region, postal code, two-letter country |
@id |
Our default | A stable URL-shaped identifier, e.g. the page URL plus #location |
url |
Recommended | The working URL of this location’s page |
telephone |
Recommended | One number with country code, matching the profile |
geo |
Recommended | GeoCoordinates, at least five decimal places |
openingHoursSpecification |
Recommended | Regular hours; edge cases below |
priceRange |
Recommended | $$ or $40-80, under 100 characters |
image |
In every Google example | Crawlable photos, ideally 16:9, 4:3 and 1:1 |
sameAs, logo |
Organization properties | Maps listing, social profiles, logo file |
aggregateRating, review |
Restricted | Only for sites that review other businesses |
A complete annotated example
JSON does not allow comments, so the notes follow the code. The business is a fictional dentist in Portland, Oregon; the block goes inside a <script type="application/ld+json"> tag on the homepage or contact page.
{
"@context": "https://schema.org",
"@type": "Dentist",
"@id": "https://www.alderstreetdental.example/#location",
"name": "Alder Street Dental",
"url": "https://www.alderstreetdental.example/",
"telephone": "+15035550142",
"priceRange": "$$",
"image": [
"https://www.alderstreetdental.example/images/exterior-16x9.jpg",
"https://www.alderstreetdental.example/images/reception-4x3.jpg",
"https://www.alderstreetdental.example/images/team-1x1.jpg"
],
"address": {
"@type": "PostalAddress",
"streetAddress": "1220 SE Alder St",
"addressLocality": "Portland",
"addressRegion": "OR",
"postalCode": "97214",
"addressCountry": "US"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": 45.51782,
"longitude": -122.65321
},
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday"],
"opens": "08:00",
"closes": "17:00"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": "Friday",
"opens": "08:00",
"closes": "13:00"
}
],
"hasMap": "https://maps.google.com/?cid=1234567890123456789",
"sameAs": [
"https://www.facebook.com/alderstreetdentalpdx",
"https://www.instagram.com/alderstreetdentalpdx"
]
}
Line by line: @type is the specific subtype, not LocalBusiness. @id is the page URL with a fragment; it must stay identical on every deploy, so never derive it from a timestamp or a post ID. url must be a working link. telephone carries the country code in one unbroken string. image points at real photos on your own domain. address uses the two-letter country code; geo has five decimals with a negative longitude for the western hemisphere. Hours group the days that share a schedule; Friday differs, so it stands alone. hasMap is the Maps URL of the profile and sameAs lists profiles about this same business.
Opening hours edge cases
Regular hours are one OpeningHoursSpecification per group of days sharing a schedule; the generator collapses consecutive identical days for you. Everything else is hand editing:
- Split hours (a lunch closure): two entries for the same days, one per stretch.
- Open 24 hours:
opensat00:00andclosesat23:59, the convention in Google’s examples. - Closed all day: Google’s examples set both
opensandclosesto00:00. Omitting a day means “no information”, not “closed”. - Past midnight: Google’s late-night example gives a
closesearlier thanopenson the same day and reads it as running into the next morning. - Holidays and seasonal closures: Google’s documented pattern is an extra entry with
validFromandvalidThrough. Schema.org’sspecialOpeningHoursSpecificationvalidates but is not on Google’s supported list. Either way, set special hours in the profile too; that is where Google actually gets holiday hours for Search and Maps.
One block showing a lunch break, a closed Sunday and a winter closure:
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "09:00",
"closes": "12:30"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "13:30",
"closes": "18:00"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": "Sunday",
"opens": "00:00",
"closes": "00:00"
},
{
"@type": "OpeningHoursSpecification",
"opens": "00:00",
"closes": "00:00",
"validFrom": "2026-12-24",
"validThrough": "2027-01-02"
}
]
Hours drift more than any other property: the door changes, the profile gets updated, the footer a month later, the markup never. Treat the three as one edit.
Service-area businesses
If you serve customers at their location and hide your address on the profile, as Google’s service-area business rules usually require, the markup has a tension: Google’s LocalBusiness page requires address, and the general guidelines say not to mark up what visitors cannot see. Our approach is a PostalAddress with only addressLocality, addressRegion and addressCountry, the town you already show on the page and profile, plus areaServed for coverage. Never publish a home address you hide everywhere else.
"address": {
"@type": "PostalAddress",
"addressLocality": "Round Rock",
"addressRegion": "TX",
"addressCountry": "US"
},
"areaServed": [
{ "@type": "City", "name": "Round Rock" },
{ "@type": "City", "name": "Pflugerville" },
{ "@type": "City", "name": "Georgetown" }
]
This satisfies the schema and the validators, but we would not promise a rich result for it; the value is a consistent entity. If customers can visit your office, use the full address. The generator flags a missing street as required, which is right for storefronts; a service-area business can ignore that warning. Our service-area business guide covers the profile side.
Geo, sameAs, hasMap and images
geo should be the profile’s pin, not the parking lot: right-click the pin in Google Maps to copy coordinates, or let the generator prefill them. Google asks for at least five decimal places; the most common error is a dropped minus sign that moves a Texas business into the Indian Ocean.
sameAs is for other pages about this same entity: the Maps listing, Facebook, Instagram, LinkedIn, Yelp, Apple Maps. Not a directory that merely mentions you. hasMap takes the Maps URL of your listing, from the profile’s Share button or the prefill.
image should be photos on your own domain that Google can crawl (not blocked by robots.txt or a login) and that show the business as the page does. Google’s image guidance for local businesses recommends 16:9, 4:3 and 1:1 versions and says images must represent the marked-up content, so no stock photos. The logo goes in logo.
Ratings and reviews: the rules
Google’s LocalBusiness page now marks aggregateRating and review as recommended only for sites that capture reviews about other local businesses. Its review snippet guidelines add three rules: ratings must come directly from users; do not aggregate ratings from other websites; and when the entity being reviewed controls the reviews about itself, including through an embedded Google reviews widget, its pages are ineligible for stars. The general guidelines add that fake reviews and markup describing content not visible on the page can bring a manual action.
In practice: leave aggregateRating out. Copying your Google rating into it breaks the aggregation rule and cannot earn stars. If you collect reviews on your own site and show them on the page, you may mark up what is shown, truthfully and completely, without expecting stars. The generator’s rating fields exist for review-site owners and warn you when filled.
Match the Google Business Profile exactly
The markup’s job is to agree with the profile. Name: the real-world name, no added keywords or towns, as Google’s representation guidelines require of the profile too. Phone: the profile’s primary number, even if the site shows a tracking number. Address: same suite, same postal code. Hours: identical. url: the page the profile’s website field points at.
Fix the profile first with our Google Business Profile optimization guide, then the website text, then the schema. The generator’s prefill doubles as an audit: it copies what Google holds, so a surprising prefilled value means one of your sources is wrong. Our NAP consistency guide covers the citations that also need to agree.
Multi-location businesses: one graph, one node per location
Each location gets its own page and its own LocalBusiness node: its own @id (the page URL plus #location), url, address, phone, hours and map link. One Organization node represents the brand, with @id ending in #organization, and each location points at it with parentOrganization. A location page carries the Organization node plus its own node in one @graph; the homepage carries the Organization alone. Do not use department for branches; Google’s examples reserve it for units inside one building, like a pharmacy inside a store. Our guide to multi-location Google Business Profiles covers the profile structure this mirrors.
The graph for the practice in the next section, trimmed to the properties that carry the pattern:
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://www.northwayphysio.example/#organization",
"name": "Northway Physiotherapy",
"url": "https://www.northwayphysio.example/",
"logo": "https://www.northwayphysio.example/images/logo.png",
"sameAs": ["https://www.facebook.com/northwayphysio"]
},
{
"@type": "Physiotherapy",
"@id": "https://www.northwayphysio.example/headingley/#location",
"name": "Northway Physiotherapy",
"url": "https://www.northwayphysio.example/headingley/",
"parentOrganization": { "@id": "https://www.northwayphysio.example/#organization" },
"telephone": "+441134960123",
"address": {
"@type": "PostalAddress",
"streetAddress": "12 Otley Road",
"addressLocality": "Leeds",
"postalCode": "LS6 3AA",
"addressCountry": "GB"
},
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification", "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday"], "opens": "07:30", "closes": "20:00" }
]
},
{
"@type": "Physiotherapy",
"@id": "https://www.northwayphysio.example/wetherby/#location",
"name": "Northway Physiotherapy",
"url": "https://www.northwayphysio.example/wetherby/",
"parentOrganization": { "@id": "https://www.northwayphysio.example/#organization" },
"telephone": "+441632960421",
"address": {
"@type": "PostalAddress",
"streetAddress": "4 Market Place",
"addressLocality": "Wetherby",
"postalCode": "LS22 6LQ",
"addressCountry": "GB"
},
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification", "dayOfWeek": ["Monday", "Wednesday", "Friday"], "opens": "08:00", "closes": "18:00" }
]
}
]
}
Both clinics carry the same name because the profiles do; adding the town would break the representation guidelines unless the signage says it.
Worked example: a two-clinic physiotherapy practice
Northway Physiotherapy has run a clinic in Headingley, Leeds for years and opened a second in Wetherby last year. The website has one contact page listing both addresses, the theme injects an Organization block with the Headingley phone on every page, and both profiles point at the homepage. The Wetherby profile ranks poorly for “physiotherapist Wetherby”.
Step one is pages, not code. Each clinic gets its own page, /headingley/ and /wetherby/, with address, phone, hours, a map and photos visible in the HTML, because the markup may only describe what the page shows. The Wetherby profile’s website field now points there.
Step two is the generator. Search “Northway Physiotherapy Wetherby”, pick the listing and prefill: the type is preselected as Physiotherapy, the hours arrive from the profile, the Maps URL lands in hasMap. Compare the hours with the door; the profile said Saturday closes at 13:00, the door says 12:00, so the profile is fixed first. Repeat for Headingley.
Step three is assembly. The theme’s Organization block is switched off, because two blocks disagreeing on the phone are worse than none. Each location page gets a @graph with the Organization node and its own node; the homepage gets the Organization alone.
Step four is checking: the Rich Results Test in URL mode for each page, then Search Console’s URL Inspection with a live test and a request to index. The Google Business Profile Optimizer is run on both profiles to catch what the exercise exposed: few photos and a thin services list in Wetherby. The honest expectation: the schema alone does not move the Wetherby ranking; the new page, the corrected website field and the profile fixes are what it will be tracked against.
Validate, deploy, monitor
Use the Rich Results Test twice: in code mode before you deploy, to catch syntax and missing properties, and in URL mode after, because that shows every block on the live page, including ones a plugin or theme added. Errors block eligibility; warnings are recommended properties you chose to skip, and some (priceRange for a dentist) are fine to leave.
The schema.org validator checks the vocabulary rather than Google’s rules, so it shows properties Google ignores (areaServed, hasMap) and is better at spotting a typo in a property name. view-source: with a search for ld+json shows how many blocks a page really carries.
In Search Console, URL Inspection with “Test live URL” shows what Google rendered, and the Unparsable structured data report catches broken JSON site-wide. There is no Local Business enhancement report; its absence means nothing. After changes, request indexing and expect days, not hours.
Common mistakes
- Several blocks that disagree. A theme Organization block, a plugin’s LocalBusiness block and a pasted one, each with a different phone. Pick one source.
- The wrong type.
Organizationalone, deprecatedProfessionalService, or a parent when a specific child exists. - Copied or invented ratings. The Google rating pasted into
aggregateRating, or a made-up count; both can bring a manual action. - Mismatched details. A tracking number, an old suite, a keyword-stuffed name, hours that differ from the profile.
- Markup the page does not show. An address only in the JSON, or reviews that exist nowhere on the page.
- An unstable
@id. Generated from a build hash or post ID, so Google sees a new entity every deploy. - Broken JSON. A trailing comma or an unescaped closing script tag inside a string silently disables the block; the validators catch it.
- Images Google cannot fetch. Blocked by robots.txt, served from a CDN that rejects crawlers, or stock photos.
Do this now
- Audit first: run your homepage and contact page through the Rich Results Test in URL mode and note every block already present.
- Fix the profile, then the site text, so every detail agrees; our local SEO checklist covers the order.
- Open the LocalBusiness Schema Markup Generator, prefill from your profile, correct the type and hours, add
sameAsand images, leave the rating fields empty. - Hand-edit the edge cases: split hours, closed days, holiday closures, service-area address.
- Remove competing blocks, then place the new one on the page it describes, one per location; the platform guide has the menu paths.
- Validate the live URL, inspect it in Search Console, request indexing.
- Add a quarterly reminder alongside the profile’s special hours check; repeat step 6 whenever a fact changes.
Put this into practice
Run the free Schema Generator now
Fill the form or prefill it from your Google Business Profile and get valid JSON-LD for the right schema.org type — address, geo, opening hours, sameAs and more — ready to paste into your site.
Frequently asked questions
Does LocalBusiness schema markup improve local rankings? +
Not directly. Google has never listed it as a ranking factor, and in our experience adding it to a site that already shows a clean name, address and phone changes nothing measurable in the local pack. Its value is removing ambiguity about which business the page describes, keeping your details consistent with the Google Business Profile, and making the facts easy for search engines and AI assistants to read. Fix the profile and reviews first; add schema because it is cheap and correct.
Which page should LocalBusiness schema go on? +
The page that describes the business: the homepage or contact page for a single location, and each location's own page for a multi-location business. Google's guideline is to put structured data on the page it describes, and the facts in the markup must be visible on that page. A brand-level Organization node can sit on the homepage.
Can I put my Google rating in the schema markup? +
No. Google's review snippet guidelines say not to aggregate ratings from other websites and that reviews an entity controls about itself are not eligible for stars. Copying the Google rating and count into aggregateRating is both against the guidelines and pointless. Leave the property out unless you run a site that reviews other businesses.
Should I use LocalBusiness or a specific type like Dentist? +
Use the most specific schema.org subtype that truthfully describes your core business, mirroring your primary Google Business Profile category: Dentist, Plumber, HairSalon, AutoRepair and so on. If no subtype exists for your trade, use LocalBusiness itself and explain the service in the description property. Do not pick a neighboring type because it sounds better.
Do I need separate schema for each location? +
Yes. Each location needs its own LocalBusiness node with its own stable @id, address, phone, hours and page URL, ideally on a dedicated location page. Tie the nodes to one Organization node with parentOrganization. One block with a single address for a business that has three does not describe any of them correctly.
How often should I update the markup? +
Whenever the facts change: new hours, a new phone number, a move, a rebrand. In practice hours drift most, so we tie the check to the same calendar reminder used for Google Business Profile special hours, plus a full review once a quarter. Test the live page in the Rich Results Test after each change.
About the author. Locan Team — local SEO specialists who have spent years optimising Google Business Profiles and local rankings for real businesses, and who now publish free tools and guides on Locan. Why everything here is free →
Related guides
Keep going
How to Add Schema Markup to Your Website (WordPress, Wix, Squarespace, Shopify, Webflow, HTML)
Menu paths for adding LocalBusiness JSON-LD on WordPress, Wix, Squarespace, Shopify, Webflow and HTML sites, how to avoid duplicate schema from themes and plugins, and how to test it for local SEO.
Read the guide →NAP Consistency in Local SEO: How to Find and Fix Name, Address, and Phone Mismatches
What NAP consistency does for local SEO today, which variations Google forgives, a free audit with search operators, a fix order from Google Business Profile to aggregators, and a moves checklist.
Read the guide →How to Optimize Your Google Business Profile: The Complete Step-by-Step Guide
Every Google Business Profile field explained in the order we complete them for clients, from verification and categories to photos, posts and reviews, with a 30-minute checklist.
Read the guide →Google My Business SEO: The Complete Guide to Ranking Your Business Profile
Google My Business SEO explained: how Google ranks a Google Business Profile, the on-profile playbook in priority order, reviews, website, citations, tracking and a monthly local SEO routine.
Read the guide →The Local SEO Checklist: 60 Steps From Setup to Ranking (Free)
A free local SEO checklist: 60 tasks in seven phases, from Google Business Profile setup to reviews, citations, schema and AI visibility, with a 30/60/90-day order and the free tool for each step.
Read the guide →Google Business Profile for Service Area Businesses: Setup, Ranking, and Pitfalls
How to set up a Google Business Profile as a service-area business: the address rule, realistic service areas, why the hidden pin decides where you rank, video verification, area pages and reviews.
Read the guide →Stay in the loop
New guides and tools, straight to your inbox
One short email when a new tool ships or a guide gets a major update. No drip campaigns, nothing to buy — unsubscribe any time with one click.