gaash.ai

Technical & Structure · 8 min read · September 25, 2026 · , gaash.ai

Plumbing and heating service data as one record, marked up to match

Write the company's service data once, as one record: the business and its trades, each service with the towns it covers, the office hours and the emergency hours, the price ranges and the numbers. Then copy it, unchanged, into the pages, into JSON-LD that types the business as Plumber, HVACBusiness or both, and into the Business Profile and the directories.

Plumber, HVACBusiness or both?

Both are schema.org types, and both sit in the same place in its tree: under HomeAndConstructionBusiness, which schema.org defines as a LocalBusiness that provides services around homes and buildings. It describes Plumber as a plumbing service and HVACBusiness as a business that provides heating, ventilation and air conditioning services. Because each is a LocalBusiness, each takes the local fields: address, telephone, openingHoursSpecification, areaServed and priceRange. We read all three types on schema.org, version 30.1, when this page was written.

One node can carry both types. The JSON-LD 1.1 Recommendation lets a node take more than one type as an array, and Google's local business documentation asks for that form when a business has several types, noting that it does not support additionalType; its own example array includes Plumber. The same documentation asks for the most specific LocalBusiness subtype possible, which for a company whose core work is both trades means the pair.

Which to use follows what the company is. Write the sentence that goes under your name on the about page. "[Company] is a plumbing and heating contractor in [town]" reads as both types. "[Company] is a plumber in [town]; we also repair gas furnaces" reads as Plumber, with the furnace work as one service among the rest. The Business Profile answers the same question with its categories, which Google's guidelines have complete the sentence "This business IS a", so the about page's sentence, the profile's categories and the markup's types should agree. If answers name you for one trade only, the trade column in the article on reading your AI visibility is where it shows.

The record: one row per service

Keep the record in one file the office owns, a spreadsheet is enough, with a row for each service the company sells, and copy every page, profile and listing from it. The overview for plumbing and heating contractors says what the pages have to state; the record is where those facts are kept once, so each service has one name on its page title, in the profile's service list, in every directory and in the markup's serviceType.

The service record for a company in both trades, one row per service. The service names are examples; every value in brackets is yours.
Service, as the page names itTradeTownsHoursPrice, as the page states it
Emergency plumbing and heatingBoth[night-area towns]Around the clock, or [the hours the line is answered][after-hours rate, as on the emergency page]
Drain and sewer cleaningPlumbing[towns]Office hours[range]
Water heater repair and replacementPlumbing[towns]Office hours[range]
Furnace and boiler repairHeating[towns]Office hours, and nights as an emergency call[range]
Heat pump installationHeating[towns, which may reach farther]By appointment[range, as on the heat pump page]
Air conditioning repairHeating and cooling[towns]Office hours[range]
Bathroom remodelPlumbing[towns]By appointment[how you quote]

Two of the columns go straight into the markup. The towns become areaServed on each service, which schema.org defines on Service as the geographic area where the service is provided, so a service you run in fewer towns lists fewer. The hours split the record into the two sets the next section keeps apart. The heat pump and remodel rows take their prices from pages of their own: the heat pump page and the bathroom renovation article.

Office hours and emergency hours, stated separately

A company in both trades has two sets of hours: the office's, when a person books planned work, and the emergency line's, when a person answers and sends a technician. They belong in different places in the markup. The business node's openingHoursSpecification carries the office. The emergency service carries its own in hoursAvailable, which schema.org gives to Service as the hours during which the service is available.

For a line answered around the clock, write the day as Google's local business documentation does, opening at 00:00 and closing at 23:59, on all seven days. For a line answered only some nights, write the hours it is answered and nothing wider. Neither set goes into the markup unless a page states it in words a visitor can read, since Google's introduction to structured data says not to mark up information that is not visible.

The Business Profile holds one primary set, and Google's guidelines describe its More hours as, in general, a subset of that set, so the profile asks for a choice the markup does not. How the emergency page and the profile settle it is in the article on the emergency page.

A JSON-LD template for a company in both trades

One business node, identified by an @id, and a Service node for each service page that names the business as its provider: that is the whole graph, shown in one block so it can be read at once. On the site, each Service node sits on the page it describes. Every bracketed value is one the record holds and a visible page shows, and if no page shows a value, delete the property rather than fill it. Building entities explains the @id graph, and structured data for AI covers the types that describe only a website.

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": ["Plumber", "HVACBusiness"],
      "@id": "https://www.example.com/#business",
      "name": "[Company name, as on the trucks and the invoices]",
      "url": "https://www.example.com/",
      "telephone": "[main number, with country and area code, as on every page]",
      "address": {
        "@type": "PostalAddress",
        "streetAddress": "[only if the contact page shows it]",
        "addressLocality": "[town]",
        "addressRegion": "[state or province]",
        "postalCode": "[only if the contact page shows it]",
        "addressCountry": "[two-letter country code]"
      },
      "areaServed": ["[town]", "[town]", "[town]"],
      "openingHoursSpecification": [{
        "@type": "OpeningHoursSpecification",
        "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
        "opens": "[07:30]",
        "closes": "[17:00]"
      }],
      "priceRange": "[the range, as the pages state it]"
    },
    {
      "@type": "Service",
      "@id": "https://www.example.com/emergency/#service",
      "name": "[Emergency service, as the page heading names it]",
      "serviceType": "[Emergency plumbing and heating]",
      "url": "https://www.example.com/emergency/",
      "provider": { "@id": "https://www.example.com/#business" },
      "areaServed": ["[night-area town]", "[night-area town]"],
      "hoursAvailable": {
        "@type": "OpeningHoursSpecification",
        "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
        "opens": "00:00",
        "closes": "23:59"
      }
    },
    {
      "@type": "Service",
      "@id": "https://www.example.com/furnace-repair/#service",
      "name": "[Furnace and boiler repair]",
      "serviceType": "[Furnace and boiler repair]",
      "url": "https://www.example.com/furnace-repair/",
      "provider": { "@id": "https://www.example.com/#business" },
      "areaServed": ["[town]", "[town]"]
    },
    {
      "@type": "Service",
      "@id": "https://www.example.com/drain-cleaning/#service",
      "name": "[Drain and sewer cleaning]",
      "serviceType": "[Drain and sewer cleaning]",
      "url": "https://www.example.com/drain-cleaning/",
      "provider": { "@id": "https://www.example.com/#business" },
      "areaServed": ["[town]", "[town]", "[town]"]
    }
  ]
}

What the template leaves out is deliberate. There is no aggregateRating and no review on the business node. Google's review snippet rules say not to aggregate ratings from other websites, and a business that controls the reviews about itself is ineligible for review stars on its own pages, which Google calls self-serving. Quote reviews in the text instead, with the reviewer's first name, the date and the platform.

There is no street address unless the contact page shows one. Google's guidelines use a plumber as their example of a business run from a residential address that should clear the address from its profile; the site then shows towns rather than a street, and the markup's address carries only the town, region and country the page shows. And priceRange, which schema.org describes as the price range of the business and Google reads as a numerical range or a count of currency signs under 100 characters, stays short. A company whose jobs run from a drain cleaning to a heat pump system says more with each job's range in text on its own page.

Correct markup still switches nothing on. Google's general structured data guidelines say it enables a feature without guaranteeing one, and its page on AI features says no special schema.org data is needed for AI Overviews or AI Mode. The template's use is plainer: it is the record again, in a form any parser can read.

The same names, towns, hours and numbers everywhere else

The profile and the directories are the record's other copies. Google's guidelines ask a service business's menu to list the complete set of services it offers, not a sample of popular ones, so the profile's services are the record's rows under the record's names. Its categories are the trades, as few as describe the core business. Its service area is the towns, which the guidelines say should not reach farther than about two hours of driving from where the business is based, larger for some businesses.

The mismatches to look for in a company that does both trades:

  • One service under three names: "HVAC" on the site, "heating and cooling" on the profile, "furnace repair" in a directory. Use the record's name in all three.
  • The emergency number in the site's header and the office number on the profile, neither of them labeled. Label both, everywhere they appear.
  • "24/7" in a page banner and office hours on the profile, with no page saying which set is which.
  • A town in the profile's service area that no page names, or a page naming a town the profile leaves out.
  • A price in a directory listing, entered years ago, that no current page carries.

In our audit of 9,483 small business websites (40 trades, 90 metros, late August to early September 2026), LocalBusiness markup appeared on roughly a quarter of the sites, and a further 13.7% carried business markup missing the fields that would make it usable. Both describe every trade together; we publish no plumbing cut. A record kept once and copied everywhere answers the second directly, because the fields get filled from a record that already holds them.

Keep the towns as a list, on the service pages and in areaServed. Google's spam policies count pages targeted at specific cities that funnel visitors to one page as doorway abuse, and the record gives each town a place without giving it a page of its own.

Common questions

Can one business node carry both Plumber and HVACBusiness?

Yes. JSON-LD allows a node more than one type, written as an array, and Google's local business documentation asks for exactly that form when a business has several types, with Plumber in its own example. Use both when both trades are the company's core work and the pages say so; the about page's first sentence and the profile's categories should say the same.

What goes in serviceType?

The service's name as the record and its page give it, such as "Drain and sewer cleaning" or "Furnace and boiler repair". schema.org describes serviceType as the type of service being offered and accepts plain text, so there is no list to pick from. What matters is that the same words appear on the page, in the profile's services and in the directories.

Does the emergency service need a page of its own for its markup?

Yes, because the markup follows a page: the Service node's name, towns and hours have to be visible somewhere a visitor can read them, and the emergency page is where they are. A Service node on the homepage describing hours no page shows is markup for content that is not there, which Google's introduction to structured data says not to write.

Sources

The 13.7% figure and the quarter of sites with LocalBusiness markup are this site's own, from the audit named below, across all 40 trades; no plumbing cut is published. What each type and property means is from schema.org, read on the live pages; what Google reads and requires is from its documentation, linked where it is used. The record and the JSON-LD graph are bracketed templates.

Sources: our audit of 9,483 small-business sites, with its method · schema.org, LocalBusiness and its subtypes · schema.org, Service · JSON-LD 1.1, W3C Recommendation · Google, local business structured data · Google, introduction to structured data · Google, structured data general guidelines · Google, review snippet · Google Business Profile guidelines · Google, AI features and your website · Google Search Central, spam policies

All articles