Pick the narrowest local business schema type
The More specific type field is where most of the value sits. LocalBusiness is a valid @type and it's also the least informative thing you can say about a business. Restaurant, Dentist, Plumber, HairSalon, and SelfStorage all tell a search engine what the place does before it reads a word of your copy.
The field accepts any of about eighty schema.org types that inherit from LocalBusiness, grouped roughly as:
- Food - Restaurant, Bakery, BarOrPub, Brewery, CafeOrCoffeeShop, FastFoodRestaurant, IceCreamShop, Winery.
- Health and beauty - Dentist, Physician, Optician, Pharmacy, BeautySalon, DaySpa, HairSalon, NailSalon.
- Trades - Electrician, GeneralContractor, HVACBusiness, HousePainter, Locksmith, MovingCompany, Plumber, RoofingContractor.
- Lodging - Hotel, Motel, Hostel, BedAndBreakfast, Resort.
- Retail - Store plus around 20 subtypes: BookStore, ClothingStore, GroceryStore, HardwareStore, JewelryStore, PetStore, TireShop, and so on.
- Services - Attorney, Notary, RealEstateAgent, TravelAgency, EmploymentAgency, FinancialService, ProfessionalService, Gym.
Type something that isn't on the list and it is not emitted. You get a notice saying the value wasn't recognized and the output falls back to LocalBusiness. That's deliberate: a @type that doesn't exist on schema.org makes the entire node meaningless to a parser, and a generator that invents types to please you is a generator that ships broken markup. Bakery works. Bakeries doesn't. CoffeeShop doesn't either - the real type is CafeOrCoffeeShop.
Keep your local business schema details aligned
This is the rule that costs people rankings, and it has nothing to do with schema syntax. The name, street address, and phone number in your markup should match your Google Business Profile character for character. Not "close enough". Not "Ave" here and "Avenue" there. Not a call-tracking number in the markup and the real number on the profile.
The address is split across 5 fields because PostalAddress is a structured object, not a string:
- streetAddress -
148 Lafayette St. Suite numbers go here. - addressLocality - the city.
- addressRegion - state or region.
NY, notNew York. - postalCode - as written locally.
- addressCountry - the 2-letter ISO 3166-1 code.
US, notUSAand notUnited States.
Street address, city, and country are treated as required. Region and postal code are strongly recommended and are the 2 people skip most often.
Opening hours the parser understands
The openingHours box takes one rule per line and converts it into openingHoursSpecification, which is the shape Google documents. It is not the legacy openingHours string property.
Accepted forms:
Mo-Fr 09:00-17:00
Sa 10:00-14:00
Su closed
2-letter abbreviations and full day names both work, and so do Mon, Tues, Thur. A day range expands across the week correctly, including ranges that wrap - Fr-Mo produces Friday, Saturday, Sunday, Monday. Times shorter than 4 digits get zero-padded, so 9:00 becomes 09:00. A line marked closed publishes nothing for that day rather than emitting a zero-length interval, which is the correct behavior and not what most generators do.
A line the parser can't read is skipped with a notice quoting the line back to you. Weekdays 9-5 fails. Mo-Fr 09:00-17:00 doesn't.
Coordinates, price range, and the fields people skip
- geo - one line,
40.7191, -74.0004. Comma, semicolon, or whitespace separated. If both halves don't parse as numbers, the field is dropped and you get a notice. Coordinates help disambiguate a location whose street address is ambiguous or newly assigned. - priceRange - a short symbol range like
$$, or a currency range like$40-$120. Free text, no validation, but keep it short. - image - one absolute URL per line. Relative paths raise a notice, because a relative URL in JSON-LD resolves against the document base and breaks the moment the block is syndicated or read out of context.
- sameAs - one profile URL per line. This is the field that links your entity to entities a search engine already knows: your LinkedIn company page, your Facebook page, your Wikipedia entry if you have one. Only include profiles that unambiguously identify the same business.
The aggregateRating trap
The ratingValue, reviewCount, and bestRating fields build an aggregateRating node, and there are 2 things to know before you use them.
First, mechanics: rating value and review count are both required for the node. Supply one without the other and the whole aggregateRating is dropped rather than shipped incomplete, with a notice explaining why. bestRating defaults to 5 if you leave it blank.
Second, and more important: Google ignores self-serving reviews. A rating you publish about your own business, on your own website, is not eligible to produce a review snippet. Marking up the average of testimonials you collected yourself does not change that. The fields exist because aggregateRating is legitimate when you're a third party aggregating reviews of someone else - a directory, a review site, a comparison page. On your own site it is markup that will be read and disregarded.
A worked local business schema example
A plumber in Brooklyn, subtype Plumber:
{
"@context": "https://schema.org",
"@type": "Plumber",
"name": "Ridgewood Drain & Pipe",
"image": "https://example.com/img/storefront.jpg",
"url": "https://example.com",
"telephone": "+1 718 555 0142",
"priceRange": "$$",
"address": {
"@type": "PostalAddress",
"streetAddress": "1420 Wyckoff Ave",
"addressLocality": "Brooklyn",
"addressRegion": "NY",
"postalCode": "11237",
"addressCountry": "US"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": "40.7043",
"longitude": "-73.9089"
},
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "08:00",
"closes": "18:00"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": "Saturday",
"opens": "09:00",
"closes": "13:00"
}
],
"sameAs": ["https://www.linkedin.com/company/example"]
}
Sunday is absent because the input said Su closed. One image, so image is a string rather than an array. Both forms are valid.
One block per location. A 3-branch business gets 3 pages and 3 blocks, each with its own address and hours, not one node with 3 addresses. If you're managing structured data across dozens of location pages, BlazeHive automates SEO content end to end, schema included.