Fixing Missing or Incomplete JSON-LD
Many sites have structured data on their blog posts, because an SEO plugin adds it there, and almost nothing anywhere else. Service pages, location pages, category pages, the about page and the contact page, which hold the facts customers and AI assistants most often ask about, go undescribed.
Other sites have markup everywhere but each block is a stub with a name and nothing more. This guide shows how to measure JSON-LD coverage template by template, choose the right types for each kind of page, fill in the properties that carry real facts, and deliver the markup in the HTML every crawler receives.
What coverage actually means
JSON-LD coverage is not a count of pages with a <script type="application/ld+json">
block. A page can have one and still say almost nothing. The Agent Readiness Audit reviews coverage as
four separate questions, and a gap in any of them is worth fixing:
- Breadth. Does every template that describes something real, such as a service, a product, a location, a person or an article, output structured data?
- Type. Is the main entity described with a type that matches what the page is about, rather than a generic WebPage or a copied Article?
- Depth. Are the properties that hold the useful facts present: price, address, opening hours, author, dates, area served?
- Connection. Do the page, its main entity and the organisation behind the site link together, so a machine can see they belong to one business?
Structured data states in a fixed vocabulary what the visible page already says. It does not replace the visible text, and it must match it. Its value for AI crawlers is that it removes ambiguity: a price marked as a price, in a stated currency, attached to a named service, is harder to misread than a number in a paragraph.
Which types suit which pages
Use the most specific schema.org type that honestly describes the page's main subject. The table below
covers the templates most small-business sites have. Every page can also carry a WebPage
(or a more specific kind such as AboutPage or ContactPage) and a BreadcrumbList.
| Template | Main entity type | Properties that carry the facts |
|---|---|---|
| Home page | Organization or a LocalBusiness subtype, plus WebSite | name, url, logo, address, telephone, sameAs |
| Service page | Service | name, description, provider, areaServed, offers with price and priceCurrency |
| Location or branch page | LocalBusiness subtype such as Plumber
or Dentist | address, geo, openingHoursSpecification, telephone, parentOrganization |
| Product page | Product | name, image, sku or gtin, brand, offers with price, priceCurrency and availability |
| Category or collection page | CollectionPage with an ItemList | itemListElement with each item's url and name |
| Article, guide or news | Article or BlogPosting | headline, author, publisher, datePublished, dateModified, image |
| About page | AboutPage about the Organization | foundingDate, founder, employee or member where relevant |
| Contact page | ContactPage | the organisation's contactPoint, telephone, email, address |
| Team or author page | ProfilePage about a Person | name, jobTitle, worksFor, sameAs |
| Event page | Event | startDate, endDate, location, eventStatus, offers |
Schema.org is broader than what Google shows as rich results, and the two should not be confused.
Google removed HowTo rich results in 2023 and limits FAQ rich results to well-known authoritative government
and health sites, but HowTo and FAQPage remain valid schema.org types that
describe content accurately. Mark content up because the description is true, not only because a search
feature exists for it.
Describe the business once, refer to it everywhere
The most common coverage gap is not a missing page type but a missing publisher. Each template outputs
its own small block, and none of them says which business the page belongs to. The fix is to describe
the organisation, and the website, once in full, give each a fixed @id, and have every other
page refer to those identifiers.
- Describe the
OrganizationorLocalBusinessfully on the home page or the about page: legal or trading name, logo, address, telephone, official profiles. - Give it an identifier based on your canonical home page address, such as
https://www.example.co.uk/#organization, and never change it. - On every other page, point to it with
"publisher","provider","brand"or"isPartOf"as appropriate, using only{"@id": "โฆ"}. - Do the same for the
WebSite, and point eachWebPageat it withisPartOf.
How identifiers work in detail, and the problems that occur when references point to nothing, are covered in the schema references guide. For coverage, the point is simply that every template should connect to the same business rather than float on its own.
A complete block for a service page
Service pages are the template most often left bare, and often the one that answers the questions people put to AI assistants: what does it cost, where do they work, who provides it. This example for a fictional drainage company shows a service page described fully and connected to the business.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebPage",
"@id": "https://www.example.co.uk/services/cctv-drain-survey/#webpage",
"url": "https://www.example.co.uk/services/cctv-drain-survey/",
"name": "CCTV drain surveys in Leeds and Wakefield",
"isPartOf": { "@id": "https://www.example.co.uk/#website" },
"mainEntity": { "@id": "https://www.example.co.uk/services/cctv-drain-survey/#service" },
"breadcrumb": { "@id": "https://www.example.co.uk/services/cctv-drain-survey/#breadcrumb" }
},
{
"@type": "Service",
"@id": "https://www.example.co.uk/services/cctv-drain-survey/#service",
"name": "CCTV drain survey",
"serviceType": "Drain inspection",
"description": "Camera inspection of domestic drains with a written report and recorded footage.",
"provider": { "@id": "https://www.example.co.uk/#organization" },
"areaServed": [
{ "@type": "City", "name": "Leeds" },
{ "@type": "City", "name": "Wakefield" }
],
"offers": {
"@type": "Offer",
"price": "145.00",
"priceCurrency": "GBP",
"description": "Standard domestic survey, including VAT"
}
},
{
"@type": "BreadcrumbList",
"@id": "https://www.example.co.uk/services/cctv-drain-survey/#breadcrumb",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Services", "item": "https://www.example.co.uk/services/" },
{ "@type": "ListItem", "position": 2, "name": "CCTV drain survey" }
]
}
]
}
</script>
The organisation and website are not repeated here; they are described on the home page and referenced by identifier. Every value in the block, including the price and the towns served, must also appear in the visible text of the page. If the price changes, the markup must change with it, which is a strong argument for generating it from the same data the page uses.
The gaps audits find most often
| Gap | What it looks like | Fix |
|---|---|---|
| Uncovered templates | Posts have markup; services, locations, categories and contact have none | Add a template-level block for each, using the table above |
| Stub entities | A LocalBusiness with a name and nothing else | Add address, telephone, opening hours and url from your business details |
| Wrong main type | Product and service pages marked as Article because the
plugin default was left on | Set the type per template, not site-wide |
| Markup injected by a tag manager | Present in the browser inspector, missing from the page source | Print it in the server HTML; many AI crawlers do not run scripts |
| Orphaned entities | Each block describes something but none names the business | Reference
the organisation by @id from every template |
| Stale values | Price, hours or availability in the markup no longer match the page | Generate markup from the same fields the page displays |
| Parallel duplicates | Theme, plugin and app each output their own version of the same entity | Choose one source per type and switch off the others |
Building a coverage matrix
Coverage is a template problem, so measure it by template. A simple spreadsheet is enough.
- List each template down the side: home, service, location, product, category, article, about, contact, team, event.
- For each, take one representative URL and fetch the raw HTML, so you see what non-rendering crawlers see:
curl -s https://www.example.co.uk/services/cctv-drain-survey/ \ | grep -c 'application/ld+json' - Paste the URL into the Schema Markup Validator at validator.schema.org, which checks against the full schema.org vocabulary, and note every type found and its properties.
- Where Google offers a rich result for the type, also run the Google Rich Results Test, which checks Google's required and recommended properties.
- Add columns for the four questions: breadth, type, depth and connection. Mark each cell as done, partial or missing.
Compare the validator results with what you see in the browser inspector. Markup that appears in the inspector but not in the curl output is being added by JavaScript, and should be moved to the server. The finished matrix becomes your work list and, later, your regression check.
The fix, step by step
Step 1
List your templates and what each page is about
Write down every page template on the site and, for each one, the real-world thing its pages describe: a service, a product, a branch, a person, an article, a list of items or the business itself. Include the pages people forget, such as contact, about, team and location pages. This list decides which schema.org type each template needs, so describe the subject in plain words before thinking about vocabulary.
Step 2
Record what markup each template outputs now
For one URL per template, fetch the raw HTML with curl, count the JSON-LD blocks, and run the page through the Schema Markup Validator. Record the types and properties found in a coverage matrix. Note where markup appears only in the browser inspector, where several tools output overlapping blocks, and where a template has no markup at all. That matrix is your starting position and your plan.
Step 3
Describe the site-wide entities once
On the home page or about page, describe the organisation fully with its name, logo, address,
telephone and official profiles, and describe the website. Give each a permanent @id based
on your canonical home address. Remove partial copies of the organisation from other templates and replace
them with references to that identifier, so every page connects to one complete description of the business.
Step 4
Choose a primary type for each template
Match each template to the most specific honest schema.org type: Service
for service pages, a LocalBusiness subtype for branches, Product for products,
CollectionPage with an ItemList for categories, Article for editorial
content. Set the type at template level rather than relying on a single site-wide default, and link each
main entity to its WebPage with mainEntity. Where a page covers several things,
pick the one it is mainly about.
Step 5
Fill in the properties that carry the facts
For each type, add the properties that hold what customers ask about: prices with currency, addresses, opening hours, areas served, availability, authors and dates. Take every value from the same CMS fields the visible page uses so the two cannot drift apart. Leave out anything not shown on the page. Check Google's documentation for required properties where a rich result exists for that type.
Step 6
Output the markup from the server
Move any JSON-LD currently added through a tag manager or client-side script into the server-rendered template, so it is present in the first HTML response. Where a theme, plugin and app each output the same type, choose one source and switch off the others. Fetch each template with curl and confirm the block appears once, is complete, and matches the visible text.
Step 7
Validate every template and repeat on change
Run each representative URL through the Schema Markup Validator and, where relevant, the Rich Results Test, and fix every error. Update the coverage matrix to show each template as covered. Run the Agent Readiness Audit again to confirm the coverage findings have cleared. Keep the matrix and repeat the check whenever a template, theme, plugin or product data feed changes.
What JSON-LD will and will not do
It is worth being clear about expectations. Structured data is a way of stating facts precisely. It does not make a thin page useful, and it does not oblige any search engine or AI system to show or cite your page.
- Google uses structured data to understand pages and to decide eligibility for rich results, subject to its own guidelines and quality systems.
- AI providers say little publicly about how they use structured data. Markup that is present in the server HTML and matches the visible text is, at worst, harmless to them and, at best, a clear statement of your facts.
- Your own tools can use it too. Well-formed markup makes it easier to feed product data, build internal search or check that prices on the site are consistent.
1. Which pages should have JSON-LD?
Every template whose pages describe something real should have it: the home page for the organisation and website, service and product pages, location or branch pages, articles, category pages, and the about, contact and team pages. Utility pages such as the basket, account area or internal search results usually do not need their own markup. Coverage is measured by template, so fixing the service template once covers every service page built from it.
2. Is a WebPage block on every page enough?
It is a start, but it describes only the page, not the thing the page is about. A service page marked only as a WebPage tells a machine nothing about the service, its price or where it is offered. Add a main entity of the appropriate type, such as Service, Product or LocalBusiness, fill in the properties that hold the facts, and link it to the WebPage with the mainEntity property so the relationship between page and subject is stated.
3. Do AI crawlers read JSON-LD added by Google Tag Manager?
Google renders JavaScript and can process structured data added by scripts, although it recommends server output where possible. Many AI crawlers read only the HTML the server sends and do not run scripts, so markup added by a tag manager may never reach them. If you want the markup to be available to every crawler, print it in the page template on the server. Compare the curl output with the browser inspector to see which method your site uses.
4. Should I still use FAQPage and HowTo markup?
They remain valid schema.org types, and marking up content that really is a set of questions and answers or a set of steps is accurate. What changed is Google's display: HowTo rich results were removed in 2023, and FAQ rich results are now limited to well-known authoritative government and health sites. So do not add them expecting enhanced search listings. Add them only where the content genuinely has that shape, and keep them consistent with the visible text.
5. What is the difference between the Schema Markup Validator and the Rich Results Test?
The Schema Markup Validator checks your markup against the whole schema.org vocabulary and shows every type and property it finds, whether or not Google does anything with them. The Rich Results Test checks only the types that can produce Google rich results and reports Google's required and recommended properties for those. Use the validator to confirm your coverage and structure, and the Rich Results Test for pages where a Google feature is relevant, such as products or events.
6. How many JSON-LD blocks can one page have?
There is no fixed limit, and several blocks on one page are valid. Problems arise when two blocks describe the same thing differently, for example a theme and a plugin both outputting an Article with different authors or dates, or two Organization descriptions with different names. A single block containing an @graph with each entity described once is the tidiest approach, but separate blocks are fine as long as they do not contradict each other.
7. Can I add markup for reviews or ratings I hold on another site?
No. Structured data should describe what the page itself shows. Marking up ratings or reviews that visitors cannot see on the page, or that were collected and are displayed elsewhere, goes against Google's structured data guidelines and can lead to the markup being ignored or to a manual action. If reviews matter to your business, display them on the page in text that the server sends, and then mark up what is shown.
The Agent Readiness Audit requests your page with real AI crawler user agents, checks robots.txt and llms.txt, reviews JSON-LD coverage and semantic HTML, tests how much depends on JavaScript and measures how easily the content can be extracted.
Open Agent Readiness Fixes โ