Back to News & Insights
JavaScript September 11, 2026 · 6 min read

Scoring a Local Business Page for the Signals Local Search Reads (the Check Validators Skip)

Schema validators tell you whether your LocalBusiness JSON-LD is well-formed. They do not tell you...

Scoring a Local Business Page for the Signals Local Search Reads (the Check Validators Skip)

Schema validators tell you whether your LocalBusiness JSON-LD is well-formed. They do not tell you whether it agrees with the page it sits on, and that disagreement is the thing local search actually penalises: a phone number in the markup that differs from the one in the footer is two claims about the same business, not one.

This post is a small Node script that scores a page for the local signals a crawler reads, including the one check the validators skip. About 80 lines, no dependencies, runs against a URL or against the three fixtures at the bottom.

Local ranking is decided per business entity, not per page. A crawler assembles the entity from the Google profile, the directories and the website, and counts agreement. So the page-level checks that matter are: Is there valid JSON-LD at all? Is there a LocalBusiness node, or a subtype (Dentist, Plumber, Restaurant), rather than only Organization or WebPage? Does it carry the fields that describe a local entity: name, address.streetAddress, address.addressLocality, telephone, opening hours, geo, url? Does the visible page say the same phone and the same street as the markup? Is the type specific? A bare LocalBusiness is valid but tells a crawler less than Dentist. The mapping from real categories to subtypes is in LocalBusiness schema types.

Check 4 is the one worth the script. The validator guide explains why Schema Markup Validator, the Rich Results Test and Search Console all stop at syntax: they have no idea what the page says in prose.

Two implementation notes. The phone comparison strips everything but digits on both sides, so +1 555 010 0100 in the markup matches +1 (555) 010-0100 in the footer; formatting differences are not inconsistencies, different numbers are. And text() removes script bodies before flattening the HTML, otherwise the JSON-LD would "agree with itself" and every page would pass check 4.

A complete page, a page with only Organization markup, and a page whose markup disagrees with its own footer:

The third fixture is the interesting one. Every validator on the market passes it: the JSON-LD is well-formed and the required properties exist. The page still says 14 Bridge St and 010-0199 where the markup says 12 Bridge Street and 010-0100. A crawler that reads both now holds two addresses and two phones for one business, and the markup you added to make the entity clearer has made it less clear.

It scores one page. Local search scores an entity across every page and every directory that describes it, and a page can be 12/12 while the same business reads differently on the profiles and listings around it. The page check is the cheap first step; the cross-source check is the rest of the job, and it is most of the work in local SEO once the profile is claimed. For a single-location owner doing it alone, the order that pays off, with the hours per step, is in local SEO for small business.

Extend it as you like: add areaServed for service area businesses, check that url equals the canonical, or fetch the sitemap and score every location page in one run. The point of keeping it small is that you can read all of it and trust what it says.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation