Guides/Technical fixesupdated 2026-08-04 · 5 min

Service and AggregateRating Schema for Local Businesses

The short answer

Service schema goes on each individual service page and states what the service is, who provides it, where it is available, and optionally what it costs. AggregateRating should only be used for genuine reviews you actually display on your own site — fabricated or unverifiable ratings are a structured-data policy violation.

LocalBusiness schema answers "who is this business." Service schema answers "what does it do, specifically, and where."

That second question is the one AI assistants are actually resolving when someone asks who replaces water heaters in Tampa.

Where Service schema goes

On individual service pages. One block per page, describing the service that page is about.

Not on your homepage — that carries LocalBusiness with a makesOffer summary. Not on your generic "Our Services" overview page, which describes several things and therefore describes none of them precisely.

The pattern:

  • Homepage: LocalBusiness (specific type) with makesOffer listing services briefly
  • /water-heater-replacement: Service schema in depth
  • /emergency-plumbing: Service schema in depth
  • /trenchless-sewer-repair: Service schema in depth

The template

{
  "@context": "https://schema.org",
  "@type": "Service",
  "@id": "https://carterplumbing.com/water-heater-replacement/#service",
  "name": "Water Heater Replacement",
  "serviceType": "Water Heater Installation and Replacement",
  "description": "Same-day replacement of gas and electric tank water heaters and conversion to tankless systems, including code-required venting, gas line, and expansion tank work.",
  "provider": {
    "@type": "Plumber",
    "@id": "https://carterplumbing.com/#business",
    "name": "Carter Plumbing & Drain",
    "telephone": "+1-813-555-0100"
  },
  "areaServed": [
    { "@type": "City", "name": "Tampa" },
    { "@type": "City", "name": "Brandon" },
    { "@type": "City", "name": "Riverview" }
  ],
  "offers": {
    "@type": "Offer",
    "priceCurrency": "USD",
    "priceSpecification": {
      "@type": "PriceSpecification",
      "minPrice": 1400,
      "maxPrice": 2600,
      "priceCurrency": "USD"
    },
    "availability": "https://schema.org/InStock"
  },
  "hasOfferCatalog": {
    "@type": "OfferCatalog",
    "name": "Water Heater Services",
    "itemListElement": [
      { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Gas Tank Water Heater Replacement" } },
      { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Electric Tank Water Heater Replacement" } },
      { "@type": "Offer", "itemOffered": { "@type": "Service", "name": "Tankless Water Heater Conversion" } }
    ]
  }
}

The fields that carry weight

provider with a matching @id. This is the important structural detail. Referencing the same @id you used in your homepage LocalBusiness block tells a parser that this service and that business are connected — one entity, not two. Most implementations skip this and lose the connection.

areaServed. Repeat it here, per service. You may not offer every service in every city, and stating it per-service is more accurate than inferring from the business-level list.

description. Plain, specific, in customer language. This is the sentence most likely to be paraphrased.

priceSpecification with a range. If you publish prices — and in most trades you should — this makes them machine-readable. Use minPrice and maxPrice, not a single figure, and make the numbers match what the page says.

hasOfferCatalog. Useful when one service page covers several variants. It lets you enumerate them without creating a page for each.

AggregateRating: the honest rules

This is where people get themselves in trouble.

AggregateRating markup states an average rating and a review count for your business or service. Used correctly it can produce star ratings in search results. Used incorrectly it is a structured data policy violation that can earn a manual action.

Use it only when all of these are true:

  1. You display genuine reviews on your own site, visible to visitors on that page.
  2. The rating and count in the markup match what is displayed exactly.
  3. The reviews are real and you can substantiate them.
  4. The markup is on the page where those reviews appear.

Do not:

  • Mark up your Google rating on your own site. Those reviews live on Google; Google already knows about them. Marking them up on your domain is claiming someone else's data as your on-page content.
  • Invent an average.
  • Mark up a rating with no visible reviews.
  • Mark up a self-serving rating on every page of the site.

Honestly: for most local service businesses, AggregateRating is not worth the risk. Your Google reviews are already visible to every system that matters — Gemini and AI Overviews read them directly, and ChatGPT and Perplexity read the pages that display them. Duplicating the number in your own markup adds little and can cost you.

If you do collect and display genuine reviews on your own site, mark them up properly:

{
  "@context": "https://schema.org",
  "@type": "Service",
  "name": "Water Heater Replacement",
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.8",
    "reviewCount": "37",
    "bestRating": "5",
    "worstRating": "1"
  },
  "review": [
    {
      "@type": "Review",
      "author": { "@type": "Person", "name": "Jim R." },
      "datePublished": "2026-07-14",
      "reviewRating": { "@type": "Rating", "ratingValue": "5", "bestRating": "5" },
      "reviewBody": "Replaced our 50-gallon heater the same day we called and caught that the expansion tank was shot."
    }
  ]
}

Individual Review objects with real text are more useful to an AI system than a bare average anyway — the text is what gets extracted.

Other schema worth adding

BreadcrumbList on every page below the homepage. Helps parsers understand your site structure and is trivially easy.

Article with a named author on blog and guide content. On health, legal, and financial topics this matters considerably.

Person for clinicians, attorneys, and licensed professionals, linked from the business schema via employee.

Organization rather than LocalBusiness if you operate several locations, with each location as its own LocalBusiness referencing the parent.

Validating

Run every page through:

  • Google Rich Results Test — what Google parses
  • Schema.org Validator — stricter, catches more

Fix errors. A block with a syntax error is silently ignored, which means you can have perfect-looking markup doing absolutely nothing.

Also check your rendered page source for duplicate blocks. SEO plugins frequently inject their own schema, and two conflicting descriptions of the same service is worse than one.

What to do this week

  1. List the five services you most want to be called for.
  2. Make sure each has its own page.
  3. Add Service schema to each, with provider referencing your business @id.
  4. Add areaServed per service.
  5. Add priceSpecification if you publish prices.
  6. Leave AggregateRating alone unless you genuinely display reviews on-site.
  7. Validate every page and check for duplicate blocks.

Then check the rest

Schema completeness is one of fifteen on-page signals we check. Our free scan runs all of them alongside twenty-five real customer questions across five AI assistants.

About fifteen seconds, free, no account.

Common questions

What's the difference between Service schema and makesOffer?
makesOffer inside your LocalBusiness block lists your services briefly at the business level. Service schema on an individual page describes that one service in depth — its area served, provider, description, and price. Use both: makesOffer for the summary, Service for the detail page.
Can I add AggregateRating to my LocalBusiness schema?
Only if you genuinely display those reviews on your own site and the numbers are accurate. Marking up a rating that appears nowhere on the page, or that you compiled yourself without a verifiable source, violates structured data guidelines and risks a manual action.
Should I include prices in Service schema?
If you publish prices, yes — machine-readable pricing is unusually valuable in trades where price opacity is the norm. Use a range via minPrice and maxPrice rather than a single figure, and make sure it matches the price shown on the page.
Do I need Service schema on every page?
On every page that describes a distinct service you want to be found for. A generic 'Services' overview page does not need it; the individual water heater replacement page does.
Stop guessing

See exactly what the five AIs say about your business.

Twenty-five real customer questions across Claude, ChatGPT, Gemini, Perplexity, and Google AI Overviews. Free, about fifteen seconds, no account.

Run the free scan

Keep reading