Local Business Schema Markup: The Complete Guide

Learn how to implement LocalBusiness schema markup with JSON-LD. Exact code examples for different business types, opening hours, departments, and common mistakes.

Google’s knowledge panel is prime real estate for any local business. When someone searches for your business name, or for a type of business in your area, that panel pulls in details like your address, hours, phone number, and reviews. Local business schema markup is how you feed Google this information directly, in a format it can parse without guessing.

Without it, Google has to piece together your business details from whatever it can crawl on your page. Sometimes it gets it right. Often it doesn’t. Structured data removes the ambiguity.

This guide walks through the exact JSON-LD you need, how to adapt it for different business types, and the mistakes that cause markup to fail validation.

What LocalBusiness schema markup actually does

According to Google’s structured data documentation, LocalBusiness structured data lets you “tell Google about business hours, different departments within a business, reviews (if your site captures reviews about other businesses), and more.”

When users search for businesses on Google Search or Maps, search results may display a prominent Google knowledge panel with details about a business that matched the query. When users search for a type of business (for example, “best NYC restaurants”), they may see a carousel of businesses related to the query.

Google’s structured data search gallery describes the Local business feature as: “Business details displayed in the Google knowledge panel, including open hours, ratings, directions, and actions to book appointments or order items.”

The LocalBusiness type on Schema.org is defined as “a particular physical business or branch of an organization.” It inherits from both Organization and Place in the schema hierarchy, which means it can carry properties from both. Your business gets a name, contact info, and brand identity from Organization, plus a physical location, geo coordinates, and opening hours from Place.

This dual inheritance is what makes LocalBusiness so useful for local SEO. It bundles everything a search engine needs to understand what your business is, where it operates, and when it’s open into a single structured block.

Google’s documentation is clear about what’s mandatory versus nice-to-have. The required properties for LocalBusiness are:

  • address (PostalAddress): The physical location of the business. Include as many sub-properties as possible: streetAddress, addressLocality, addressRegion, postalCode, and addressCountry.
  • name (Text): The name of the business.

That’s the minimum to be eligible for rich results. But stopping there means leaving most of the value on the table. The recommended properties are where the real benefit lives:

  • aggregateRating: Only for sites that capture reviews about other businesses.
  • geo (GeoCoordinates): Latitude and longitude. This helps Google place your business precisely on a map.
  • openingHoursSpecification: Your business hours, broken down by day.
  • telephone: A phone number customers can call.
  • url: The URL of your business website.
  • image: Photos of your business. Google’s example markup includes multiple aspect ratios (1x1, 4x3, 16x9).
  • priceRange: A rough indicator like ”$” or ”$$$”.
  • department: For businesses with distinct departments that have their own hours or contact details.

The basic JSON-LD template

Here’s the foundational structure. Every local business schema starts with this pattern, placed in a <script type="application/ld+json"> tag in your page’s <head>:

{
  "@context": "https://schema.org",
  "@type": "LocalBusiness",
  "name": "Your Business Name",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "123 Main Street",
    "addressLocality": "Your City",
    "addressRegion": "ST",
    "postalCode": "12345",
    "addressCountry": "US"
  },
  "geo": {
    "@type": "GeoCoordinates",
    "latitude": -33.8688,
    "longitude": 151.2093
  },
  "url": "https://www.yourbusiness.com",
  "telephone": "+61212345678",
  "image": [
    "https://www.yourbusiness.com/photos/storefront.jpg"
  ]
}

The @context and @type fields are always present. The @context tells parsers this uses the Schema.org vocabulary. The @type tells them what kind of thing you’re describing.

Pick the most specific business type

This is the single most important decision in your markup. Google’s documentation states explicitly: “Use the most specific LocalBusiness sub-type possible; for example, Restaurant, DaySpa, HealthClub, and so on.”

Schema.org lists dozens of LocalBusiness subtypes. A restaurant shouldn’t use LocalBusiness as its type. It should use Restaurant. A dentist should use Dentist. An electrician should use Electrician.

Why does specificity matter? Because @type is how Google categorizes your business. A generic LocalBusiness type tells Google you’re… a business. Somewhere. A Dentist type tells Google exactly what you do, which directly affects which queries you’re eligible to appear for.

Google’s example in their docs uses Restaurant as the type, not LocalBusiness:

{
  "@context": "https://schema.org",
  "@type": "Restaurant",
  "name": "Dave's Steak House",
  "servesCuisine": "American",
  "priceRange": "$$$"
}

Notice how Restaurant unlocks type-specific properties like servesCuisine and menu that wouldn’t be relevant for a generic LocalBusiness.

If your business spans multiple categories, Google supports specifying multiple types as an array. Their documentation provides this exact pattern:

{
  "@context": "https://schema.org",
  "@type": ["Electrician", "Plumber", "Locksmith"]
}

Google notes that additionalType isn’t supported for this purpose. Use the array syntax instead.

Opening hours: more complex than you’d think

Opening hours look simple until you have to handle late nights, seasonal closures, or 24-hour operations. Google’s documentation covers each scenario with specific patterns.

Standard hours use openingHoursSpecification with dayOfWeek, opens, and closes:

"openingHoursSpecification": [
  {
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
    "opens": "09:00",
    "closes": "21:00"
  },
  {
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": ["Saturday", "Sunday"],
    "opens": "10:00",
    "closes": "23:00"
  }
]

You can group days with the same hours into a single specification, as shown above, or list each day separately.

Late night hours (past midnight) use a single OpeningHoursSpecification. Google’s example defines hours from Saturday at 6pm until Sunday at 3am:

"openingHoursSpecification": {
  "@type": "OpeningHoursSpecification",
  "dayOfWeek": "Saturday",
  "opens": "18:00",
  "closes": "03:00"
}

The key detail: you don’t need separate entries for Saturday and Sunday. A closes time earlier than opens tells Google the business stays open past midnight.

24-hour businesses set opens to "00:00" and closes to "23:59". To show a business is closed all day, set both to "00:00":

"openingHoursSpecification": [
  {
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": "Saturday",
    "opens": "00:00",
    "closes": "23:59"
  },
  {
    "@type": "OpeningHoursSpecification",
    "dayOfWeek": "Sunday",
    "opens": "00:00",
    "closes": "00:00"
  }
]

Seasonal hours add validFrom and validThrough properties. Google’s example shows a business closed for winter holidays:

"openingHoursSpecification": {
  "@type": "OpeningHoursSpecification",
  "opens": "00:00",
  "closes": "00:00",
  "validFrom": "2015-12-23",
  "validThrough": "2016-01-05"
}

Excluding validFrom and validThrough signifies that the hours are valid year-round.

Marking up departments

Larger businesses often have departments with their own hours, phone numbers, or specialties. A department store might have a pharmacy that closes earlier than the main store. A medical centre might have a pathology lab with different contact details.

Google’s documentation supports this through the department property. You nest each department inside the parent business, and only specify the properties that differ. Here’s the structure Google uses in their example, with a Store containing a Pharmacy department:

{
  "@context": "https://schema.org",
  "@type": "Store",
  "name": "Dave's Department Store",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "1600 Saratoga Ave",
    "addressLocality": "San Jose",
    "addressRegion": "CA",
    "postalCode": "95129",
    "addressCountry": "US"
  },
  "telephone": "+14088717984",
  "openingHoursSpecification": [
    {
      "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
      "opens": "08:00",
      "closes": "23:59"
    }
  ],
  "department": [
    {
      "@type": "Pharmacy",
      "name": "Dave's Pharmacy",
      "telephone": "+14088719385",
      "openingHoursSpecification": [
        {
          "@type": "OpeningHoursSpecification",
          "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
          "opens": "09:00",
          "closes": "19:00"
        }
      ]
    }
  ]
}

Each department gets its own @type, and Google’s example uses Pharmacy as a specific subtype rather than a generic label. The department inherits the parent’s address but defines its own hours and phone number.

Common mistakes that break your markup

Getting the JSON-LD syntax right is only half the battle. These are the errors that pass syntax checks but still prevent your markup from working as intended.

Using LocalBusiness when a specific subtype exists. If you run a dental practice and mark it as LocalBusiness, you’re giving Google less information than your competitors who use Dentist. Always check Schema.org’s list of LocalBusiness subtypes for a match before defaulting to the generic type.

Missing the address structure. The address must be a PostalAddress object, not a plain text string. Writing "address": "123 Main St, City, State" is technically valid Schema.org but won’t satisfy Google’s requirement for structured address components.

Forgetting geo coordinates. The geo property with latitude and longitude is recommended, not required, but it removes any ambiguity about your physical location. Without it, Google has to geocode your address, which introduces potential errors.

Inconsistent NAP data. Your name, address, and phone number in the schema should match exactly what appears on your Google Business Profile and across other directories. Google’s guidance on local rankings states that “businesses with complete and accurate info are more likely to show up in local search results.” Conflicting data between your schema and your GBP listing creates friction rather than reinforcement.

Using additionalType for multiple business types. Google explicitly notes that additionalType isn’t supported for specifying multiple business types. Use an array in the @type field instead.

Not validating before deploying. Google recommends using the Rich Results Test to validate your code and fix critical errors before deployment. You can also use the Schema Markup Validator to check your markup against the full Schema.org specification.

How LocalBusiness schema connects to your Google Business Profile

Your Google Business Profile and your website’s LocalBusiness schema serve different purposes, but they reinforce each other. The GBP is what Google uses to populate the local pack and Maps results. Your on-site schema gives Google structured confirmation of the same data.

Google determines local rankings based on three factors: relevance, distance, and prominence. Schema markup strengthens the relevance signal. When your on-site schema confirms the same business name, address, phone number, and business category that your GBP declares, Google has higher confidence in all of that data.

Think of it as corroboration. One data source is good. Two consistent data sources are better. This is especially important for businesses that operate across multiple service areas, where location pages with matching schema help establish relevance for each area you serve.

Where to place the markup on your site

Google’s documentation states: “You can add LocalBusiness structured data to any page on your site, though it may make more sense to put it on a page that contains information about your business.”

For most businesses, the right pages are:

  • Your homepage, if it contains your address, hours, and contact details.
  • Your contact or about page, where business details are typically listed.
  • Individual location pages, if you have multiple branches. Each location gets its own LocalBusiness block with its specific address, hours, and phone number.

For multi-location businesses, don’t put all locations in a single schema block on one page. Each location page should carry its own markup, with the address and details specific to that branch.

Testing and deploying your markup

Google outlines a clear deployment process:

  1. Add the required properties and follow the guidelines.
  2. Validate your code using the Rich Results Test and fix any critical errors.
  3. Deploy to a few pages first and use the URL Inspection tool to test how Google sees the page.
  4. Make sure the page is accessible to Google and not blocked by a robots.txt file, the noindex tag, or login requirements.
  5. Submit a sitemap to keep Google informed of future changes.

After deploying, monitor your Search Console for any structured data errors. Google reports validation issues under the Enhancements section, and you’ll see whether your LocalBusiness markup is being recognized and whether any properties are missing or malformed.

The gap between having schema and not having it isn’t dramatic for every business. But for businesses competing in local search, where Google’s ranking factors include relevance and prominence, structured data is one of the few things that’s entirely within your control. It takes thirty minutes to implement correctly, and once it’s in place, it works continuously without ongoing maintenance (barring changes to your hours or address).

Get the type right. Get the data consistent. Validate before you deploy. That’s the entire playbook.

Your profile goes live in minutes.