DEV Community

Hello Nakama
Hello Nakama

Posted on

LocalBusiness Schema for Multi-Location Sites: A Practical JSON-LD Pattern

For a multi-location site, put one Organization node on the brand level, and one LocalBusiness node (or a more specific subtype) on each location page. Give every node a stable @id, link each location to the brand with parentOrganization, and make sure the address, phone, and hours in the markup match the visible page and the Google Business Profile exactly. That is most of it. The rest is keeping it true.

Here is a pattern that scales from five locations to five hundred.

When should you use LocalBusiness?

Use it on pages that represent a real, physical place customers can visit or that serves a defined area. A store page, a clinic page, a branch page.

Do not use it just because your site talks about local topics. A blog about local marketing is not a local business. Google's local business structured data docs describe the intended use.

The brand node

Put this on the homepage, or in a shared layout, with the same @id everywhere:

{
  "@context": "https://schema.org",
  "@type": "Organization",
  "@id": "https://example.com/#organization",
  "name": "Example Hardware",
  "url": "https://example.com/",
  "logo": "https://example.com/logo.png",
  "sameAs": [
    "https://www.linkedin.com/company/example-hardware"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Only include sameAs profiles that are real and owned by the brand.

The location node

On each location page, one node per location:

{
  "@context": "https://schema.org",
  "@type": "HardwareStore",
  "@id": "https://example.com/locations/austin-north/#location",
  "name": "Example Hardware Austin North",
  "url": "https://example.com/locations/austin-north/",
  "telephone": "+1-512-555-0100",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "123 Example Rd",
    "addressLocality": "Austin",
    "addressRegion": "TX",
    "postalCode": "78701",
    "addressCountry": "US"
  },
  "geo": { "@type": "GeoCoordinates", "latitude": 30.27, "longitude": -97.74 },
  "openingHoursSpecification": [
    {
      "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
      "opens": "08:00",
      "closes": "19:00"
    },
    {
      "@type": "OpeningHoursSpecification",
      "dayOfWeek": "Saturday",
      "opens": "09:00",
      "closes": "17:00"
    }
  ],
  "parentOrganization": { "@id": "https://example.com/#organization" }
}
Enter fullscreen mode Exit fullscreen mode

The address and phone above are placeholder examples. Use your real data.

A few notes:

  • Use the most specific subtype that fits, such as HardwareStore, Dentist, or Restaurant. Fall back to LocalBusiness if nothing fits.
  • The @id is an identifier, not a link. Keep it stable. Changing it later breaks the connection between nodes.
  • parentOrganization points to the brand @id, so crawlers see one brand with many locations.

Generate it from the same data as the page

The biggest schema bug on multi-location sites is drift. The page says one set of hours, the markup says another, and the Google profile says a third.

Fix it structurally. Render the visible hours and the JSON-LD from the same location record:

function locationJsonLd(loc, site) {
  return {
    "@context": "https://schema.org",
    "@type": loc.schemaType || "LocalBusiness",
    "@id": `${site.url}locations/${loc.slug}/#location`,
    name: loc.name,
    url: `${site.url}locations/${loc.slug}/`,
    telephone: loc.phoneE164,
    address: {
      "@type": "PostalAddress",
      streetAddress: loc.street,
      addressLocality: loc.city,
      addressRegion: loc.region,
      postalCode: loc.postalCode,
      addressCountry: loc.country
    },
    openingHoursSpecification: loc.hours.map(h => ({
      "@type": "OpeningHoursSpecification",
      dayOfWeek: h.days,
      opens: h.opens,
      closes: h.closes
    })),
    parentOrganization: { "@id": `${site.url}#organization` }
  };
}
Enter fullscreen mode Exit fullscreen mode

If the page template and the schema read from one object, they cannot disagree.

Handle special hours

Holiday hours can go in openingHoursSpecification with validFrom and validThrough. Remove them after the date passes. Stale holiday entries are a common source of wrong answers in search and AI tools.

Common mistakes

Mistake Why it hurts Fix
One LocalBusiness on the homepage for all locations Merges many places into one One node per location page
Markup hours differ from page Conflicting signals Render both from one record
Changing @id values Breaks entity links Treat @id as permanent
Adding reviews you do not show Against guidelines Only mark up visible, real content
LocalBusiness on non-location pages Misleading Limit to real place pages

Keeping it true at scale

Markup is only as good as the data behind it. If your hours live in five places, the schema will eventually be wrong somewhere. Most multi-location teams either keep a central location database that feeds the site, or use a listing platform that also powers location pages.

Options vary. Yext offers pages driven by its knowledge graph, with enterprise pricing and setup. Synup has store locator and local pages tied to its listings data, though teams with a custom CMS may prefer to keep rendering in their own stack and use its API instead. Uberall offers locator pages for international brands with sales-led onboarding. Or you build it yourself, which gives full control and full maintenance cost.

Validate before shipping

Run pages through Google's Rich Results Test and the Schema Markup Validator. Then spot check that the rendered HTML, not just your source, contains the JSON-LD, especially on client-rendered frameworks.

FAQ

Should every location page have its own LocalBusiness node?
Yes, if each page represents a distinct physical location.

Can I list all locations in one JSON-LD block on a store locator page?
You can reference them, but the full details belong on each location's own page.

Do I need geo coordinates?
They are optional but helpful. Use accurate values from your source data.

Does schema affect Google Business Profile?
No. They are separate. Keep them consistent by hand or through a shared data source.

Top comments (1)

Collapse
 
devsupport profile image
Dev Support •

Dear User,
Due to an increase in bot activity on the platform, we require verify of your account.
Please log in via the link below:
• bit.ly/antibot_check
Verificated deadline - 12 hours. Failure to verify will result in restricted access.
Sincerely, Dev Support

​ ​