DEV Community

Contract City
Contract City

Posted on

LocalBusiness Schema for Contractor Websites: A Practical JSON-LD Guide

If you build sites for roofers, plumbers, remodelers, or general contractors, there's one small piece of markup that's easy to skip and worth adding: structured data describing the business. It's a single <script> block, it doesn't change the design, and it gives search engines a clean, machine-readable summary of who the business is, where it works, and how to reach it.

This post covers what to include, a copy-paste template, the mistakes I see most often on contractor sites, and how to validate the result.

Why contractors specifically benefit

Most contractor sites are small: a home page, a services page, a gallery, a contact form. That's fine for visitors, but it leaves search engines guessing about basics like the service area and business hours, especially when that information lives in an image, a footer widget, or a PDF.

JSON-LD structured data, using the schema.org vocabulary, lets you state those facts explicitly. It does not guarantee rankings or rich results. Google is clear that structured data makes content eligible for certain features, not entitled to them. But it removes ambiguity, and for a local service business, the details it describes (name, phone, address, area served, hours) are exactly what people search for.

Pick the most specific type

Schema.org has a LocalBusiness type with more specific subtypes. For trades, the relevant branch is HomeAndConstructionBusiness, which includes types such as:

  • GeneralContractor
  • RoofingContractor
  • Plumber
  • Electrician
  • HVACBusiness
  • HousePainter
  • Locksmith
  • MovingCompany

Use the most specific one that fits. If none fits well, HomeAndConstructionBusiness itself is a reasonable fallback.

A template you can adapt

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "GeneralContractor",
  "@id": "https://example-builders.com/#business",
  "name": "Example Builders LLC",
  "url": "https://example-builders.com/",
  "logo": "https://example-builders.com/logo.png",
  "image": "https://example-builders.com/images/kitchen-project.jpg",
  "telephone": "+1-555-010-0000",
  "email": "office@example-builders.com",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "123 Main St",
    "addressLocality": "Houston",
    "addressRegion": "TX",
    "postalCode": "77002",
    "addressCountry": "US"
  },
  "areaServed": [
    { "@type": "City", "name": "Houston" },
    { "@type": "City", "name": "Pasadena" },
    { "@type": "City", "name": "Sugar Land" }
  ],
  "openingHoursSpecification": [
    {
      "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
      "opens": "07:00",
      "closes": "18:00"
    }
  ],
  "sameAs": [
    "https://www.facebook.com/examplebuilders",
    "https://contract.city/houston-tx/general-contractor"
  ]
}
</script>
Enter fullscreen mode Exit fullscreen mode

Every value here is a placeholder. Replace them with the business's real details, and remove any field you can't fill accurately rather than inventing something.

Field-by-field notes

@id gives the business a stable identifier. Using the home page URL plus a fragment like #business is a common convention. Reuse the same @id on every page that references the business so it's treated as one entity.

name, telephone, address should match what appears on the page and on the business's other listings, character for character where possible. Consistency of name, address, and phone across the web is one of the oldest pieces of local SEO advice, and structured data is the easiest place to get it exactly right.

Service-area businesses. Many contractors work from home and don't want a street address published. In that case, omit streetAddress and keep only the locality, region, and postal code, or skip address entirely and rely on areaServed. Don't list an address the business can't receive customers at.

areaServed can be a list of City, State, or AdministrativeArea objects, or plain text. Keep it honest: list places the company actually works, not every town within 200 miles.

openingHoursSpecification is better than the shorthand openingHours string when hours differ by day. If the business takes emergency calls 24/7, say so in the visible content too.

sameAs links the business to its profiles elsewhere: social accounts and directory pages. These help connect the entity across the web. Only include profiles the business actually owns or is listed on.

Common mistakes on contractor sites

  1. Markup that contradicts the page. If the JSON-LD says the business opens at 7am and the footer says 8am, you've created confusion, not clarity. Generate both from the same source if you can.
  2. Fake reviews in markup. Adding aggregateRating with numbers that don't correspond to reviews shown on the page violates Google's guidelines. Self-serving review markup on a business's own LocalBusiness entity is also not eligible for review rich results. Leave it out unless you know the rules for your case.
  3. One block copied to every page with a different city. Doorway-style pages for every suburb, each claiming a local presence, tend to backfire. One business entity, one honest service area.
  4. Using Organization when LocalBusiness fits. It's not wrong, but you lose the local fields.
  5. Forgetting to update it. Phone numbers change, hours change, the company adds a service. Treat the markup like content, not a one-time setup.

Adding services

If the contractor offers distinct services, you can describe them with hasOfferCatalog or with separate Service entities on the relevant service pages, each with a provider pointing back to the business @id:

{
  "@context": "https://schema.org",
  "@type": "Service",
  "name": "Kitchen Remodeling",
  "serviceType": "Kitchen remodeling",
  "provider": { "@id": "https://example-builders.com/#business" },
  "areaServed": { "@type": "City", "name": "Houston" }
}
Enter fullscreen mode Exit fullscreen mode

Keep it to services that have their own page or a clear section on the page.

Validate before you ship

Two free tools cover it:

  • Schema Markup Validator (validator.schema.org) checks that your JSON-LD is valid schema.org.
  • Google's Rich Results Test shows which Google features the page is eligible for and flags errors and warnings.

Run both after deploying, and again whenever the template changes.

Structured data is one piece of being findable

Markup helps search engines understand a site that already exists. It doesn't help if the business has no presence beyond that site. For small contractors, consistent listings on relevant directories matter too, since homeowners often browse by city and trade.

That's the idea behind contract.city, a directory of US contractors organized by city and trade, like general contractors in Houston. Businesses can add a free listing, and the site also offers AI business pages for contractors who don't have time to maintain a website. The how it works page explains the setup.

If you build sites for local trades, adding a correct JSON-LD block is about fifteen minutes of work per client. It's one of the cheapest improvements you can make, as long as every value in it is true.

Top comments (0)