A local business website is a small site with a short job: say who you are, where you are, when you are open, how to reach you and how to book. An agent asked "find me a physiotherapist near the office who is open on Saturday and book a slot" needs exactly those facts, in a form it can read. Most of the 27 checks in the rubric apply to a five-page site the same way they apply to a five-thousand-page one, but for a local business five of them do most of the work. This post covers those five, with the markup.
LocalBusiness schema, and why it is not enough on its own
The natural type for a local business is LocalBusiness, with its subtypes for dentists, restaurants, plumbers and the rest. It carries the fields an agent wants: address, telephone, openingHoursSpecification, geo and priceRange. Publish it. But publish it beside an Organization or WebSite node, for a reason that is specific to how the scanner reads types.
Check B2, "correct schema types for the site type", 5 points, scores two halves. The first 2 points are for a sitewide node, and the engine tests for the literal type names Organization or WebSite across the JSON-LD on every crawled page. In schema.org's hierarchy LocalBusiness is a subtype of Organization, but the engine compares names, not the hierarchy, so a page whose only type is LocalBusiness does not satisfy the sitewide half. Check D2 does the same: its identity half needs the type Organization with a sameAs. The remaining 3 points of B2 are for type-specific markup, and for a site classed as "other", which is where a typical local business lands, the accepted types are Article, FAQPage or SoftwareApplication (the engine also accepts Service). LocalBusiness is not on that list either.
Two things follow. Give the business node both types, or publish an Organization node alongside it; the engine flattens @graph and reads arrays of types, so either works. And add an FAQPage, which a local business has the material for anyway: parking, what to bring, whether you take walk-ins, cancellation terms. That is the type-specific half of B2 without inventing content.
{
"@context": "https://schema.org",
"@graph": [
{
"@type": ["LocalBusiness", "Organization"],
"@id": "https://example.com/#org",
"name": "Example Physio",
"url": "https://example.com/",
"telephone": "+65 6000 0000",
"address": {
"@type": "PostalAddress",
"streetAddress": "10 Example Road, #02-01",
"addressLocality": "Singapore",
"postalCode": "000000",
"addressCountry": "SG"
},
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "08:00",
"closes": "19:00"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": "Saturday",
"opens": "09:00",
"closes": "13:00"
}
],
"sameAs": ["https://www.example-directory.test/example-physio"]
},
{
"@type": "WebSite",
"name": "Example Physio",
"url": "https://example.com/"
},
{
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Do you take walk-ins?",
"acceptedAnswer": { "@type": "Answer", "text": "Yes, on weekdays before 11:00." }
}
]
}
]
}
Put it in the template so it appears on every page, not the homepage only; B1 wants valid JSON-LD on half or more of the crawled pages. Keep name identical to the title element and og:site_name, because D5 compares them. And make sure the JSON parses: one trailing comma and B1 scores the page as if the block were absent.
A contact page
Check D2, "verifiable identity", 4 points, Low effort. The first 2 points come from a contact route, and the test is generous: an href whose path contains /contact, /about, /impressum or /legal, or any mailto: link, on any page the crawl fetched. A local business site normally has a contact page already; the work is making sure it is linked from the footer of every page and that the link is in the server-rendered HTML rather than injected by a script.
The second 2 points need the Organization type with at least one sameAs URL. For a local business the obvious candidates are a directory profile, a social page, or a registry entry. Use one that exists and resolves. Verifiable identity explains why a sameAs pointing at a page nobody created is worse than leaving it out.
Put the phone number and address in the page text as well as the schema. An agent reading the HTML gets both, and a person gets what they came for.
Booking without an embed
The booking widget is the point where a small site most often makes itself harder for agents, and it is worth understanding why. An embedded calendar is a third-party script on every page that renders it, and often an overlay. Check A6, "no hard interstitial", 2 points, tests the homepage against a list of markup patterns for challenge pages, CAPTCHA loaders and consent dialogs; a cookie wall alone scores 1 of 2, a CAPTCHA or challenge scores 0. A booking overlay is not on that list by name, but it belongs to the same family: a rendered viewport dominated by something the agent cannot dismiss. The widget's content is also missing from the raw HTML the plain fetch receives, which is check A7's territory, and the script usually sets cookies, which is how the consent banner arrives.
The alternative is a link. Our booking page is a server-rendered page with a button that opens the calendar provider in a new tab, and the page says why: an embed is a third-party script on every page that renders it and a potential viewport overlay, and both A6 and A7 get worse for a conversion gain we are not convinced is real. A local business can do the same. The page carries the hours, the address, the phone number and a plain link to the booking provider; the provider does the booking on its own domain. Nothing is lost that the agent could have used, and the page an agent receives is the page.
The same logic applies to the chat widget and the map. Link out, or embed nothing that draws over the content. Cookie walls are interstitials covers the consent side of this in detail.
A privacy notice
Check D3, "policies discoverable", 4 points. The engine looks for an href containing privacy on the crawled pages for 2 points, and for a non-commerce site an href containing terms, tos or conditions for the other 2. It does not read the pages. What the notice must say depends on where you are and what you collect, and that is a question for your adviser and your regulator; what the scanner can check is that the link exists on every page. Put both in the footer.
A robots.txt that names the crawlers
Check A2, 6 points, the heaviest single check in the rubric, parses robots.txt for six named agents and scores proportionally to how many are permitted. A small site rarely writes its own robots.txt; it inherits one from the builder or a plugin, and some of those inherit a block. Write one that says what you mean.
User-agent: GPTBot
User-agent: OAI-SearchBot
User-agent: ClaudeBot
User-agent: Claude-User
User-agent: PerplexityBot
User-agent: Google-Extended
Allow: /
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml
The Sitemap line is the third point of A3. Then confirm the edge agrees: A5 fetches the homepage as three of those crawlers and records the status codes, and a builder or CDN that blocks bots by default will return 403 whatever the file says.
Check your own site
Run the free scan and read B2, D2 and A6 with their evidence. The definitions are on the methodology page under understanding, trust and freshness and discovery and access.