Schema markup is one of those topics that sounds scarier than it actually is. If you don’t know what JSON-LD is, you’re about to. And if you’re a contractor whose site has no schema at all, you’re leaving real ranking signal and rich-result opportunities on the table.
So let me do a quick plain-English explainer, then hand you the copy-paste templates. This post skews slightly nerdier than the rest of the Get Found pillar, but I’m still going to explain the vocabulary before using it. Deal?
Okay, so what is schema markup actually?
Schema.org is a shared vocabulary (co-maintained by Google, Microsoft, Yahoo, and Yandex) for describing things on the web in a way machines can understand. “This page is about a business. That business is a plumbing contractor. It operates in these zip codes. It has this phone number. Here are its reviews.”
You, the human, already know all that from reading the page. Search engines have to infer it. Schema markup just tells them, explicitly, in a format they’re built to consume.
JSON-LD (JavaScript Object Notation for Linked Data) is the format Google strongly recommends for adding schema to your site. It lives inside a <script type="application/ld+json"> tag in your page’s <head>. Invisible to visitors. Fully visible to Google.
Per Google Search Central’s own docs, structured data can make your pages eligible for rich results and can improve click-through rates. Google doesn’t guarantee a rich result will appear, but eligibility requires well-formed schema that matches their type-specific guidelines.
Why it matters specifically for a service-area business
A service-area business (SAB) is any business where you go to the customer instead of the customer coming to you. Plumbers, HVAC techs, roofers, electricians, mobile pet groomers, tree services. This matters for schema because you don’t have a storefront to ground the “location” signal on. You have a service area.
The areaServed property in LocalBusiness schema is how you tell search engines exactly where you work. Get this right and you’re helping Google understand which searches to show you for. Leave it vague or omit it and you’re relying entirely on Google guessing from context.
For the full GBP-side of defining your service area, we’ve got the Google Business Profile setup guide. On-site schema is the other half of that same signal.
The FAQ schema caveat (important)
Before we get to templates. Quick heads-up that used to be a go-to for local sites and isn’t anymore.
In August 2023, Google announced changes to FAQ and HowTo rich result visibility. FAQ rich results now display primarily for well-known authoritative government and health websites. Your small-business site is no longer reliably eligible for the FAQ rich result in the SERP. You can still add FAQPage schema, and it doesn’t hurt you, but don’t expect the accordion-style FAQ result to appear in search.
HowTo schema was similarly restricted and is now even rarer as a rich result. The takeaway: don’t spend time on FAQ/HowTo schema hoping for rich results. Focus on LocalBusiness, Service, and AggregateRating instead, where the eligibility is still real.
Template 1: LocalBusiness (with service-area-business settings)
This is the foundation schema for every page on your site, ideally loaded via your theme so it outputs in the <head> sitewide. Per Google’s LocalBusiness structured data docs, you should use the most specific subtype available. Plumber, HVACBusiness, RoofingContractor, Electrician are all valid @type values.
Copy-paste template. Replace the placeholder values with your actual business info.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Plumber",
"@id": "https://www.yourbusiness.com/#business",
"name": "Your Business Name",
"url": "https://www.yourbusiness.com/",
"telephone": "+1-555-123-4567",
"email": "[email protected]",
"image": "https://www.yourbusiness.com/images/logo.jpg",
"priceRange": "$$",
"address": {
"@type": "PostalAddress",
"streetAddress": "123 Main St",
"addressLocality": "Columbus",
"addressRegion": "OH",
"postalCode": "43215",
"addressCountry": "US"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": 39.9612,
"longitude": -82.9988
},
"areaServed": [
{ "@type": "City", "name": "Columbus" },
{ "@type": "City", "name": "Dublin" },
{ "@type": "City", "name": "Westerville" },
{ "@type": "City", "name": "Gahanna" },
{ "@type": "City", "name": "Upper Arlington" }
],
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
"opens": "07:00",
"closes": "18:00"
},
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": "Saturday",
"opens": "08:00",
"closes": "14:00"
}
],
"sameAs": [
"https://www.facebook.com/yourbusiness",
"https://www.google.com/maps/place/?q=place_id:ChIJxxxx",
"https://www.yelp.com/biz/yourbusiness"
]
}
</script>A few notes on this template:
@type: use the most specific one available. For HVAC it’sHVACBusiness. For roofing it’sRoofingContractor. Google’s full list is in their LocalBusiness docs.address: even for service-area businesses, include your real address. If you’re hiding it on GBP because you work from home, that’s fine, you can still include it in schema (it helps Google ground your business geographically).areaServed: list every city you actually service. This is your SAB signal. Don’t stretch.sameAs: include links to your authoritative profiles elsewhere (Facebook, GBP, Yelp, BBB). This is how you tell Google “these listings are all the same entity.”
Template 2: Service (one per individual service page)
If you’ve got dedicated pages for specific services (water heater installation, AC repair, roof replacement), each one should have Service schema in addition to the sitewide LocalBusiness schema.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Service",
"serviceType": "Water Heater Installation",
"provider": {
"@type": "Plumber",
"@id": "https://www.yourbusiness.com/#business"
},
"areaServed": [
{ "@type": "City", "name": "Columbus" },
{ "@type": "City", "name": "Dublin" },
{ "@type": "City", "name": "Westerville" }
],
"hasOfferCatalog": {
"@type": "OfferCatalog",
"name": "Water Heater Services",
"itemListElement": [
{
"@type": "Offer",
"itemOffered": {
"@type": "Service",
"name": "Tankless Water Heater Installation"
}
},
{
"@type": "Offer",
"itemOffered": {
"@type": "Service",
"name": "Traditional Tank Water Heater Installation"
}
},
{
"@type": "Offer",
"itemOffered": {
"@type": "Service",
"name": "Water Heater Repair"
}
}
]
}
}
</script>The @id reference in the provider section is important. It links this Service back to your main business entity. Don’t duplicate the whole LocalBusiness block inside each Service schema; reference it by @id so Google understands it’s the same business.
Template 3: AggregateRating (pair with your LocalBusiness)
If you have real reviews (on Google, on your own site, somewhere) you can mark up an aggregate rating. Critical caveat: this has to be reviews you actually collected, displayed on the page where the schema appears. Don’t fabricate. Don’t pull numbers from nowhere. Google’s guidelines specifically prohibit fake ratings, and the enforcement has gotten stricter.
Add this inside your LocalBusiness schema object (not as a separate block):
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.9",
"reviewCount": "247",
"bestRating": "5",
"worstRating": "1"
}The ratingValue and reviewCount should match what’s actually visible on the page. If you display “4.9 stars across 247 Google reviews” in your hero, your schema should say the same thing.
Template 4: BreadcrumbList (for every non-homepage page)
Breadcrumbs help Google understand your site hierarchy and can improve how your URLs display in search results.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://www.yourbusiness.com/"
},
{
"@type": "ListItem",
"position": 2,
"name": "Services",
"item": "https://www.yourbusiness.com/services/"
},
{
"@type": "ListItem",
"position": 3,
"name": "Water Heater Installation",
"item": "https://www.yourbusiness.com/services/water-heater-installation/"
}
]
}
</script>How do I actually add these to my WordPress site?
A few options, in rough order of complexity:
- Use an SEO plugin. Yoast SEO, Rank Math, and SEOPress all include automatic schema output. They won’t give you perfect SAB schema out of the box, but they’ll cover LocalBusiness basics.
- Use a dedicated schema plugin. Schema Pro and similar plugins give you more control over the specific properties.
- Hard-code it into your theme’s functions.php. For a fully custom, correct implementation, drop a
wp_headhook that outputs the JSON-LD. This is how well-built child themes like the ones we build handle it. - Hire someone. If schema is going to be one of ten things on your list that you’ll maybe get to in three months, it’s often faster to hand it off.
How do I test that my schema is actually working?
Two tools, both free, both from Google. Run every page through both after you deploy.
- Google Rich Results Test: search.google.com/test/rich-results. Paste your URL. It’ll show you what structured data Google detected and flag any errors.
- Schema Markup Validator: validator.schema.org. This one validates against the full schema.org vocabulary, not just Google’s rich-result-eligible subset. Useful for catching issues the Rich Results Test doesn’t flag.
If you get warnings (yellow) in the Rich Results Test, those are optional properties you could add to improve eligibility. If you get errors (red), those are real problems that need fixing. Start with the errors.
The common mistakes I keep seeing
After auditing a lot of contractor sites, here are the recurring schema mistakes:
- Multiple conflicting LocalBusiness blocks. The SEO plugin generates one. The theme generates another. They contradict each other. Pick one source of truth. Disable the other.
- Wrong
@type. Generic “LocalBusiness” when “Plumber” or “HVACBusiness” would be more specific and more useful. - Missing or incorrect
areaServed. Either absent entirely or set to a single city when you service ten. - Fabricated aggregate ratings. Don’t do this. Google’s guidelines explicitly prohibit it and the penalty is real.
- Schema that doesn’t match what’s on the page. Your schema says you have 5.0 stars with 400 reviews. Your page displays 4.7 stars with 52 reviews. Google cross-checks this. Keep them in sync.
- Stale schema after a move or rebrand. Old phone number. Old address. Old business name. Audit your schema every time you audit your website.
What to do this week
If you’ve read this far and don’t currently have schema on your site, here’s your shortlist:
- Add LocalBusiness schema to your homepage using the template above. Replace the placeholders.
- Run the Rich Results Test. Fix any errors.
- If you have service pages, add Service schema to each one.
- If you display reviews on your site, add AggregateRating inside your LocalBusiness schema. Make sure the numbers match what’s on the page.
- Verify every page by running it through the Rich Results Test before you call it done.
Schema is one of the few SEO levers where the work genuinely compounds. Set it up well once and it runs forever. And for a service-area business, well-formed areaServed is telling Google exactly where you want to show up, which is worth doing even if none of the rest of this post made sense.
The companion pieces to this one are our local service business website checklist (the 47-item version) and the Google Business Profile setup guide. Together they cover the Get Found pillar end-to-end.
If you want this done for you as part of a full site build or SEO engagement, we handle it in our SEO service and bake it into every website we build. You can also see how this all fits together on the how it works page, or browse the portfolio for examples in the wild.
Schema isn’t glamorous. But invisible code that feeds Google exactly what it needs is, honestly, one of the better returns on a Saturday afternoon you’ll ever get.