Search "dentist near me" on your phone. The first results aren't websites — they're a map with three pins and a list with ratings, hours, and a phone number you can tap to call right there. That's the Local Pack. Getting into it is mostly about Google Business Profile, but Local Business schema on your website is the supporting signal that connects your GBP listing to your domain and strengthens the whole thing.
For businesses with a physical location or a defined service area, this is the schema type with the most direct connection between implementation and visible search behavior. Address, hours, phone, star ratings — all of it comes from data you provide.
What's covered: the right business subtype (there are over 100 options), opening hours edge cases that trip up most implementations, how multi-location businesses should structure their schema, and code examples for Next.js, React, and WordPress.
What Is Local Business Schema?
LocalBusiness is a schema.org type that describes a physical business or service area business — any organization that serves customers at or from a geographic location. It's a subtype of Organization, which means it inherits all Organization fields and adds location-specific ones.
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Corner Coffee Roasters",
"address": {
"@type": "PostalAddress",
"streetAddress": "42 Market Street",
"addressLocality": "Portland",
"addressRegion": "OR",
"postalCode": "97201",
"addressCountry": "US"
},
"telephone": "+15034567890",
"openingHours": "Mo-Fr 07:00-18:00"
}
Unlike Organization schema which describes a brand entity, LocalBusiness schema describes a specific location. A chain with five locations should have five separate LocalBusiness schema blocks — one per location page.
LocalBusiness Subtypes: Choose the Most Specific One
Schema.org has over 100 specific subtypes of LocalBusiness. The more specific your type, the better Google can categorize your business. Always use the most specific type that accurately describes what you do.
| Category | Subtypes |
|---|---|
| Food & drink |
Restaurant, Bakery, Bar, CafeOrCoffeeShop, FastFoodRestaurant
|
| Health |
Dentist, MedicalClinic, Pharmacy, Optician, Physician
|
| Automotive |
AutoDealer, AutoRepair, GasStation, ParkingFacility
|
| Finance |
AccountingService, InsuranceAgency, LegalService
|
| Retail |
ClothingStore, ElectronicsStore, GroceryStore, BookStore
|
| Home services |
HVACBusiness, Plumber, Electrician, RoofingContractor
|
| Beauty |
HairSalon, BeautySalon, NailSalon, SpaOrHealthClub
|
| Sports |
GolfCourse, GymOrHealthClub, SkiResort
|
| Accommodation |
Hotel, Motel, BedAndBreakfast, Hostel
|
| Professional |
AccountingService, LegalService, FinancialService, RealEstateAgent
|
If your specific business type isn't in the list, go one level up. A tattoo studio might use LocalBusiness directly since there's no TattooStudio type. A software development agency is better served by ProfessionalService than LocalBusiness.
If your business genuinely belongs to more than one category — for example a coffee shop that is also a bookstore — schema.org supports multiple types as an array:
{
"@context": "https://schema.org",
"@type": ["CafeOrCoffeeShop", "BookStore"],
"name": "Read & Brew"
}
Use this sparingly and only when both types genuinely apply.
What Rich Results Local Business Schema Enables
| Feature | What appears | Requirement |
|---|---|---|
| Address in results | Street, city, region |
address with PostalAddress
|
| Phone click-to-call | Tap to call on mobile | telephone |
| Opening hours | "Open now" / "Closes at 6pm" |
openingHours or openingHoursSpecification
|
| Star rating | ⭐ 4.3 (127 reviews) | aggregateRating |
| Map pin | Location marker in search maps |
geo with GeoCoordinates
|
| Price range | $, $$, $$$ indicator | priceRange |
| Menu / services | Link to menu page |
hasMenu (restaurants) |
| Amenities | Parking, Wi-Fi, accessibility | amenityFeature |
These features appear in Local Pack results (the map + three business listings), Google Knowledge Panels for local businesses, and standard search result snippets.
The Complete Local Business Schema
{
"@context": "https://schema.org",
"@type": "Restaurant",
"name": "Corner Coffee Roasters",
"description": "Specialty coffee roaster and café serving single-origin pour-overs, espresso drinks, and housemade pastries in Portland's Pearl District.",
"url": "https://cornercoffeeroasters.com",
"image": [
"https://cornercoffeeroasters.com/images/cafe-exterior.jpg",
"https://cornercoffeeroasters.com/images/interior.jpg",
"https://cornercoffeeroasters.com/images/coffee.jpg"
],
"logo": "https://cornercoffeeroasters.com/logo.png",
"telephone": "+15034567890",
"email": "hello@cornercoffeeroasters.com",
"address": {
"@type": "PostalAddress",
"streetAddress": "42 Market Street",
"addressLocality": "Portland",
"addressRegion": "OR",
"postalCode": "97201",
"addressCountry": "US"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": 45.5231,
"longitude": -122.6765
},
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "07:00",
"closes": "18:00"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Saturday"],
"opens": "08:00",
"closes": "17:00"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Sunday"],
"opens": "09:00",
"closes": "15:00"
}
],
"priceRange": "$$",
"servesCuisine": "Coffee, Pastries",
"hasMenu": "https://cornercoffeeroasters.com/menu",
"menu": "https://cornercoffeeroasters.com/menu",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.6",
"reviewCount": "218",
"bestRating": "5",
"worstRating": "1"
},
"sameAs": [
"https://www.google.com/maps/place/corner-coffee-roasters",
"https://www.yelp.com/biz/corner-coffee-roasters",
"https://www.instagram.com/cornercoffeeroasters",
"https://www.facebook.com/cornercoffeeroasters"
],
"paymentAccepted": "Cash, Credit Card, Apple Pay",
"currenciesAccepted": "USD",
"amenityFeature": [
{
"@type": "LocationFeatureSpecification",
"name": "Free Wi-Fi",
"value": true
},
{
"@type": "LocationFeatureSpecification",
"name": "Wheelchair accessible",
"value": true
},
{
"@type": "LocationFeatureSpecification",
"name": "Outdoor seating",
"value": true
}
]
}
Field Reference
| Field | Type | Priority | Notes |
|---|---|---|---|
name |
Text | Required | Business name as customers know it — match your Google Business Profile exactly |
address |
PostalAddress | Required | Full address with all sub-fields |
telephone |
Text | Required | E.164 format: "+15034567890" |
url |
URL | Required | Your website's homepage or location page |
openingHoursSpecification |
Array | Strongly recommended | More flexible than openingHours string — use this |
geo |
GeoCoordinates | Strongly recommended | Exact lat/lng — copy from Google Maps for precision |
image |
URL or array | Strongly recommended | Real photos of the business, not stock images |
aggregateRating |
AggregateRating | Strongly recommended | Enables star display — only include with real review data |
priceRange |
Text | Recommended | "$", "$$", "$$$", "$$$$" |
description |
Text | Recommended | 100–200 characters, what you offer and who you serve |
logo |
URL or ImageObject | Recommended | Business logo |
sameAs |
Array of URLs | Recommended | Google Maps, Yelp, social profiles, directory listings |
email |
Text | Optional | Public contact email |
paymentAccepted |
Text | Optional | Payment methods accepted |
amenityFeature |
Array | Optional | Wi-Fi, parking, accessibility features |
hasMap |
URL | Optional | Google Maps URL for this location |
Opening Hours: Two Formats Explained
Google supports two ways to express opening hours. Use openingHoursSpecification (the structured object array) — it's more flexible and handles edge cases like split shifts and holiday hours.
Format 1: openingHours string (simple, limited)
"openingHours": [
"Mo-Fr 09:00-18:00",
"Sa 10:00-16:00"
]
The string format uses a compact notation: two-letter day abbreviations (Mo, Tu, We, Th, Fr, Sa, Su), dash for ranges, 24-hour time. For simple Monday-Friday hours, this is fine.
Format 2: openingHoursSpecification (flexible, handles edge cases)
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday"],
"opens": "09:00",
"closes": "18:00"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Thursday", "Friday"],
"opens": "09:00",
"closes": "20:00"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Saturday"],
"opens": "10:00",
"closes": "16:00"
}
]
Use openingHoursSpecification when:
- Different days have different hours
- You have split shifts (morning and evening sessions)
- You have seasonal hours that change
24/7 businesses:
"openingHoursSpecification": {
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],
"opens": "00:00",
"closes": "23:59"
}
Closed on a specific day (e.g. Sundays):
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Sunday"],
"opens": "00:00",
"closes": "00:00"
}
Setting both opens and closes to "00:00" is Google's documented way to indicate a day is closed. Don't omit the day or skip the entry — include it explicitly.
Temporarily closed:
"openingHours": "closed"
Or use temporarilyClosed: true as a boolean flag.
Geo Coordinates: Getting Them Right
The geo field with exact latitude and longitude is one of the most impactful fields for local search accuracy. Google uses coordinates to determine proximity in "near me" searches.
"geo": {
"@type": "GeoCoordinates",
"latitude": 45.52310,
"longitude": -122.67650
}
How to get precise coordinates:
- Open Google Maps and find your business location
- Right-click on the exact spot (center of your building entrance works best)
- The coordinates appear at the top of the context menu — click to copy
- Use 5+ decimal places for accuracy
Don't use your city center coordinates or a rounded approximation. The precision matters for "near me" proximity calculations, especially in dense urban areas where multiple competitors may be within a block.
Service Area Businesses: When You Don't Have a Storefront
A plumber, house cleaner, or mobile dog groomer serves customers at their location, not at a fixed address. These are Service Area Businesses (SABs). The schema approach differs:
{
"@context": "https://schema.org",
"@type": "Plumber",
"name": "Quick Fix Plumbing",
"telephone": "+15034567890",
"url": "https://quickfixplumbing.com",
"areaServed": [
{
"@type": "City",
"name": "Portland"
},
{
"@type": "City",
"name": "Beaverton"
},
{
"@type": "AdministrativeArea",
"name": "Multnomah County"
}
],
"serviceArea": {
"@type": "GeoCircle",
"geoMidpoint": {
"@type": "GeoCoordinates",
"latitude": 45.5231,
"longitude": -122.6765
},
"geoRadius": "30000"
}
}
Key differences for SABs:
-
addressis optional — many SABs prefer not to show their home address publicly - Use
areaServedto list cities and regions where you work - Use
serviceAreawithGeoCircleto define a radius in meters - Don't include
geoif you're not showing a physical address
In Google Business Profile, SABs set their service area rather than a storefront address. Your schema should mirror this setup.
Multi-Location Businesses
A chain with multiple locations should have a separate LocalBusiness JSON-LD block on each location's individual page. Don't try to express multiple locations in a single schema block.
Root organization page (e.g., brand.com):
{
"@context": "https://schema.org",
"@type": "Corporation",
"name": "Corner Coffee Roasters",
"url": "https://cornercoffeeroasters.com",
"numberOfLocations": 5
}
Individual location page (e.g., brand.com/portland):
{
"@context": "https://schema.org",
"@type": "CafeOrCoffeeShop",
"name": "Corner Coffee Roasters — Portland",
"parentOrganization": {
"@type": "Corporation",
"name": "Corner Coffee Roasters",
"url": "https://cornercoffeeroasters.com"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "42 Market Street",
"addressLocality": "Portland",
"addressRegion": "OR",
"postalCode": "97201",
"addressCountry": "US"
},
"telephone": "+15034567890",
"openingHoursSpecification": [...]
}
parentOrganization links each location to the parent brand. This helps Google understand the relationship between the locations and the overarching organization.
Implementation in Next.js (App Router)
For a single-location business with a dedicated location page:
// app/location/page.tsx (or just app/page.tsx for single-location)
export default function LocationPage() {
const localBusinessSchema = {
'@context': 'https://schema.org',
'@type': 'LocalBusiness',
name: 'Acme Services',
description: 'Professional web development and consulting services.',
url: 'https://acmeservices.com',
telephone: '+15034567890',
address: {
'@type': 'PostalAddress',
streetAddress: '42 Market Street',
addressLocality: 'Portland',
addressRegion: 'OR',
postalCode: '97201',
addressCountry: 'US',
},
geo: {
'@type': 'GeoCoordinates',
latitude: 45.5231,
longitude: -122.6765,
},
openingHoursSpecification: [
{
'@type': 'OpeningHoursSpecification',
dayOfWeek: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
opens: '09:00',
closes: '18:00',
},
],
sameAs: [
'https://linkedin.com/company/acmeservices',
'https://www.google.com/maps/place/acmeservices',
],
}
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(localBusinessSchema) }}
/>
{/* page content */}
</>
)
}
For dynamic multi-location pages, fetch location data from your CMS and map it to the schema structure in the server component.
Implementation in React (with react-helmet-async)
import { Helmet } from 'react-helmet-async'
function LocationPage({ location }) {
const schema = {
'@context': 'https://schema.org',
'@type': location.businessType || 'LocalBusiness',
name: location.name,
description: location.description,
url: location.url,
telephone: location.phone,
address: {
'@type': 'PostalAddress',
streetAddress: location.street,
addressLocality: location.city,
addressRegion: location.state,
postalCode: location.zip,
addressCountry: location.country,
},
geo: {
'@type': 'GeoCoordinates',
latitude: location.lat,
longitude: location.lng,
},
openingHoursSpecification: location.hours,
aggregateRating: location.rating
? {
'@type': 'AggregateRating',
ratingValue: location.rating.value.toString(),
reviewCount: location.rating.count.toString(),
}
: undefined,
}
return (
<>
<Helmet>
<script type="application/ld+json">
{JSON.stringify(schema)}
</script>
</Helmet>
{/* page content */}
</>
)
}
Implementation in WordPress
For a single-location business, add to functions.php:
function add_local_business_schema() {
if ( ! is_front_page() && ! is_page( 'contact' ) ) return;
$schema = [
'@context' => 'https://schema.org',
'@type' => 'LocalBusiness',
'name' => get_bloginfo( 'name' ),
'description' => get_bloginfo( 'description' ),
'url' => home_url( '/' ),
'telephone' => '+15034567890',
'address' => [
'@type' => 'PostalAddress',
'streetAddress' => '42 Market Street',
'addressLocality' => 'Portland',
'addressRegion' => 'OR',
'postalCode' => '97201',
'addressCountry' => 'US',
],
'geo' => [
'@type' => 'GeoCoordinates',
'latitude' => 45.5231,
'longitude' => -122.6765,
],
'openingHours' => [
'Mo-Fr 09:00-18:00',
'Sa 10:00-16:00',
],
'sameAs' => [
'https://linkedin.com/company/your-business',
'https://www.google.com/maps/place/your-business',
],
];
echo '<script type="application/ld+json">' . wp_json_encode( $schema ) . '</script>';
}
add_action( 'wp_head', 'add_local_business_schema' );
If you use a page builder or custom fields plugin, pull the address and contact info from ACF or custom meta fields instead of hardcoding them.
The 7 Most Common Local Business Schema Mistakes
1. Name doesn't match Google Business Profile
Google cross-references your schema name against your Google Business Profile listing. If they differ — even slightly ("Joe's Plumbing" vs "Joe's Plumbing LLC") — it weakens the entity signal. Use the same name everywhere.
2. Incorrect or outdated telephone format
// Wrong — Google may not link this for click-to-call
"telephone": "503-456-7890"
// Correct — E.164 international format
"telephone": "+15034567890"
3. Not including geo coordinates
For "near me" queries, proximity is determined by coordinates, not address parsing alone. A business without geo relies entirely on Google's geocoding of the address — which is usually accurate but never as precise as explicit coordinates.
4. Fabricated aggregateRating
Only include aggregateRating if it reflects real reviews from real customers. Google can cross-reference this against your Google Business Profile rating and other review platforms. Fabricated ratings are a policy violation.
5. Same schema block for all locations
If you have multiple locations and serve them all from one schema block with one address, Google can only associate structured data with one location. Each location page needs its own, unique schema block.
6. openingHours not kept up to date
If your hours change seasonally or for holidays and your schema still shows the old hours, Google may display incorrect "Open now" / "Closed" status in search results. Use specialOpeningHoursSpecification for temporary hour changes.
7. Using LocalBusiness for a fully remote business
A SaaS company, a freelance consultant with no fixed office, or an e-commerce store with no customer-facing location should use Organization or Corporation, not LocalBusiness. Using LocalBusiness incorrectly for remote businesses can confuse Google's local index.
Local Business Schema vs Google Business Profile
A common question: do I need both? Yes, and they serve different functions.
| Google Business Profile | Local Business Schema | |
|---|---|---|
| Source | Google's own platform | Your website |
| Controls | Map pack ranking, reviews, photos | Rich result data, entity signals |
| Indexing | Managed directly by Google | Discovered when Google crawls your site |
| Updates | Near real-time | Next crawl cycle (days to weeks) |
| Reviews | Native review system | Aggregates from schema data |
Google Business Profile is the primary driver of Local Pack (map results) performance. Local Business schema on your website is a supporting signal that reinforces the entity connection between your GBP listing and your website.
Always have both. Prioritize keeping your GBP listing complete and current — then let Local Business schema on your site reinforce those signals.
Summary
Local Business schema has the most direct connection between implementation and visible search results of any schema type covered in this series. Get it right and Google has the data to show your address, hours, phone, and star rating in search results. Miss the key fields and you're leaving that display space to competitors.
The practical checklist:
- Use the most specific
@typesubtype for your business category - Match
nameexactly to your Google Business Profile - Include
geocoordinates from Google Maps — copy them precisely - Use
openingHoursSpecification(not the string format) for anything beyond simple Mon–Fri hours - Only include
aggregateRatingwith real review data - For multiple locations: one schema block per location page, use
parentOrganizationto link to the brand - For SABs: use
areaServedandserviceAreainstead of a physical address
If you want to generate Local Business schema through a structured form with live preview and missing field alerts, there's a free generator here: Local Business Schema Generator. No signup, no backend, runs locally in the browser.
Running into issues with your schema not matching your GBP listing? Drop the specifics in the comments — the discrepancy cases are often more instructive than the happy path.
Top comments (0)