Local-service websites often end up with structured data that is either missing entirely or pasted in from a generator with fields nobody can verify. Google's rich results and other consumers read that markup as claims about the business, so every field is a commitment. This article walks through a practical approach: which schema.org types to ship on a service business homepage, how to connect them into one graph with stable @id values, what to deliberately leave out, and how to validate the result. The worked example is landscaping-uae.com, a site for a UAE landscaping business whose homepage our team built and maintains. The site is Arabic, right-to-left, and targets several emirates, which makes it a useful test of the technique.
Start from the claims you can actually support
Before writing any JSON-LD, list the facts you can verify from the business itself: legal or trading name, canonical URL, logo or representative image, the geographic areas served, and the page's main topic. Each of these maps to a schema.org property. Anything you cannot verify should not appear. That includes opening hours you have not confirmed, prices, addresses you have not checked against a registration, and ratings.
A local-service site typically needs only a few types on its homepage:
-
LocalBusiness(or a more specific subtype) describing the organization. -
WebSite, which names the site and can anchor a search action if you have one. -
FAQPageonly when the page visibly contains the questions and answers in the markup. Structured data that does not match on-page content is a policy problem, not just a validation one.
The live homepage of the example site ships exactly these three types. The relevant portion of its business entity, copied from the live site and trimmed for this article, looks like this:
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"@id": "https://landscaping-uae.com/#organization",
"name": "تنسيق حدائق الامارات",
"url": "https://landscaping-uae.com/",
"image": "https://landscaping-uae.com/wp-content/uploads/2026/07/tfsyl-mthlat-khshby-brgolat-result-2-1024x572.webp",
"areaServed": [
{
"@type": "AdministrativeArea",
"name": "أبوظبي"
},
{
"@type": "AdministrativeArea",
"name": "الشارقة"
},
{
"@type": "AdministrativeArea",
"name": "دبي"
},
{
"@type": "AdministrativeArea",
"name": "عجمان"
}
]
}
The name is in Arabic because the business's public name is Arabic; the name value should match what the page displays. The areaServed list uses AdministrativeArea with names that match the emirates the site covers, which is more precise than a free-text string.
Connect the types with stable @id values
The most common mistake in multi-type markup is emitting several disconnected objects, each describing the same organization in slightly different ways. A better pattern is a single @graph where entities reference each other by @id. The @id is a URI fragment that is stable across pages; the convention in the example is /#organization for the business and /#website for the site.
A simplified example of the connection, using generic values:
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "LocalBusiness",
"@id": "https://example.com/#organization",
"name": "Example Service Co",
"url": "https://example.com/"
},
{
"@type": "WebSite",
"@id": "https://example.com/#website",
"url": "https://example.com/",
"name": "Example Service Co",
"publisher": { "@id": "https://example.com/#organization" }
}
]
}
The publisher reference tells consumers that the website belongs to the organization without repeating its full definition. Every page on the site can reuse the same @id, so the entity is defined once in your templates and referenced elsewhere. If you generate markup per page, make the ID generation deterministic from the canonical domain, not from a page slug.
Leave out what you cannot back up
Two omissions matter most for this kind of site.
First, do not ship aggregateRating or Review markup unless the business has real reviews that you can point to and that are collected under a policy you can defend. Self-authored ratings, or ratings imported from nowhere, are the fastest route to a manual action. The example site's team policy is explicit on this point: they do not ship rating or review markup unless the business has real reviews, and they do not invent opening hours, prices, or addresses in structured data. That policy is the decision; the markup simply does not contain those fields.
Second, do not fill optional fields to look complete. A trimmed snippet with accurate fields is better than a long one with guessed values. Empty-looking fields like telephone or openingHours are not required for a valid LocalBusiness, and adding placeholder values creates data that someone will eventually have to find and remove.
The sitemap and canonical setup support the same discipline. The example site splits its sitemap by content type (services, articles, pages, tags), which keeps each file's purpose clear and makes it easier to audit what is indexable. Its homepage canonical points to itself, and it declares no hreflang links, which is consistent with a single-language page strategy and avoids claiming translations that do not exist.
Validate in layers
Validation has three distinct questions, and each needs a different tool.
Syntax: is the JSON well-formed, and does every @type exist in schema.org? Paste the block into a JSON linter first. A missing comma will break the whole script block silently in most consumers.
Vocabulary and types: does the markup conform to the schema.org vocabulary, and are properties used on the types that accept them? Schema.org's own validator checks this and will flag properties attached to the wrong type.
Eligibility for rich results: does Google's Rich Results Test accept the page for the features you target? Note that a valid object can still be ineligible, and eligibility rules change, so treat the tool's output as current for the day you run it.
Also check the rendered HTML, not only the source you wrote. Templates, caching layers, and plugin filters can alter or duplicate the script tag. View the final page source on the live URL and confirm there is one coherent graph, not two competing LocalBusiness objects.
Finally, make validation part of the release process. Structured data drifts when content changes: a new service area, a renamed business, or an FAQ that was edited on the page but not in the markup. A short checklist run after each template change catches most of this before it reaches search engines.
Takeaways
- Ship only the types your page visibly supports, and keep
FAQPagealigned with on-page content. - Model the organization once with a stable
@id, and reference it from other entities instead of repeating it. - Omit ratings, reviews, hours, prices, and addresses unless you can verify them; a smaller accurate graph beats a fuller guessed one.
- Validate in layers: JSON syntax, schema.org vocabulary, then rich-result eligibility, always against the rendered page.
- Re-run validation whenever content or templates change, because structured data drifts silently.
Top comments (0)