schema markup
Structured data embedded in a page (usually as JSON-LD) that describes what the page is about in a machine-readable vocabulary defined at schema.org.
The short answer
Schema markup is structured data embedded in a page — almost always a JSON-LD block — that declares what the page is about using the schema.org vocabulary. It names an entity type and its properties, so search engines and language models read meaning directly instead of inferring it from prose, heading tags and layout.
Vocabulary, syntax, and what schema is not
Two things get collapsed into the phrase schema markup and separating them clears up most confusion. schema.org is a vocabulary: it defines types like Product, Article and Organization, and the properties each type accepts. JSON-LD, Microdata and RDFa are syntaxes: three ways of writing that vocabulary into a page. JSON-LD is the one to use — it sits in a single script block, never entangles itself with your DOM, and survives a template refactor that would shred inline Microdata attributes.
The routine confusion is with meta tags, including Open Graph and Twitter cards. Those describe the document as an object to be previewed: give me a title, an image and a description for this link. Schema describes the entities the document is about, with typed properties and relationships between them. A meta description is a string that no machine reasons over. A Product node with a price, a currency, an availability value and a linked AggregateRating is a data structure a machine can compare against another one.
The other line worth drawing is between schema and rich results. Marking a page up does not entitle you to a decorated search listing, and Google has withdrawn eligibility for whole types before — HowTo lost its rich result, and FAQ rich results were narrowed to well-known government and health sites in August 2023. The markup kept working for machine extraction the entire time. Treat rich results as an occasional bonus and extraction as the actual reason you ship structured data.
How schema coverage is measured
Schema is graded as coverage, not as presence. In the AIRank rubric it forms a 20% pillar made of ten checks, and each check asks whether the right type appears on the pages that warrant it: FAQPage on a high-intent page, Product on commerce pages, Review and AggregateRating alongside it, HowTo on step-by-step guides, Article on posts, Organization with sameAs links, BreadcrumbList on internal pages. The denominator is page templates, not individual URLs, because a type either exists in a template or is missing from every page built from it.
Two quality gates sit on top of coverage and both are pass/fail. The first is validation: the block has to parse and satisfy both the Schema.org validator and Google's Rich Results Test, because a malformed block is discarded whole rather than partially credited. The second is uniqueness: exactly one block per type per page. Good looks like every template carrying its correct type, every block validating clean, and no page emitting the same type twice.
Instrumentation
Grade one URL per template rather than crawling everything. Schema is emitted by templates, so a hundred product pages give you one result and ninety-nine copies of it — sampling by template turns a slow crawl into a five-minute audit.
A worked example
Here is a Product block with the two children that do the most work for extraction. Everything in it corresponds to something a reader can see on the page, which is not a stylistic preference — Google's structured-data policies require marked-up content to be visible, and markup that describes content the visitor cannot find is a manual-action risk rather than a clever shortcut.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"@id": "https://example.com/products/kettle#product",
"name": "Meridian 1.7L Electric Kettle",
"description": "Variable-temperature kettle with a 60-second boil hold.",
"brand": { "@type": "Brand", "name": "Meridian" },
"offers": {
"@type": "Offer",
"price": "89.00",
"priceCurrency": "GBP",
"availability": "https://schema.org/InStock",
"url": "https://example.com/products/kettle"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.6",
"reviewCount": "212"
},
"review": [{
"@type": "Review",
"author": { "@type": "Person", "name": "R. Okafor" },
"reviewRating": { "@type": "Rating", "ratingValue": "5" },
"reviewBody": "Holds temperature accurately enough for pour-over."
}]
}
</script>Three details are load-bearing. The @id gives the product a stable identifier, so a Breadcrumb or Organization block elsewhere on the site can reference this exact node instead of describing a second, unrelated product that happens to share a name. The offers object carries price and currency as separate fields, which is what lets a model answer a price question without parsing your formatting. And aggregateRating has to reflect the count actually shown on the page — inflating reviewCount is the fastest way to lose eligibility across every type you ship.
Three ways schema implementations break
- The invisible-schema trap. Someone marks up content that does not appear on the page — reviews pulled from a third-party feed, an FAQ that only exists in the JSON, a price the visitor never sees. It validates, so it looks fine. Google's structured-data policies explicitly require the marked-up content to be visible, and this is the pattern that earns manual actions. The fix is a parity check in review: every property in the block must correspond to something a person can find on the rendered page.
- The double-injection. A theme emits Article schema, an SEO plugin emits its own, and the page ships two blocks describing the same thing with different values. Parsers do not merge them; they pick or they discard, and either way you have handed a machine contradictory data about your own content. The fix is to designate exactly one emitter per type, disable schema output in everything else, and add a duplicate-type assertion to whatever test suite already runs against your templates.
- The orphan graph. Every page carries a correct block, but nothing references anything else — no @id anywhere, no Organization node, no publisher link from Article, no breadcrumb tying a product to its category. Each block is individually valid and collectively useless, because a model cannot tell that the twelve product pages belong to one business. The fix is to give every entity a stable @id and reference nodes by @id, so the site resolves into one connected graph instead of a hundred disconnected fragments.
Schema markup and its neighbours
| Term | What it optimises | How you measure it | Where it lives |
|---|---|---|---|
| Schema markup | Machine-readable meaning for every entity on a page | Type coverage per template, validation pass rate, duplicates | A JSON-LD script block in the page |
| FAQPage schema | One specific shape: authored question-and-answer pairs | Question node count and visible-parity of every answer | One block on high-intent pages |
| llms.txt | Which URLs a model should read, and in what priority | Well-formed structure and link resolution rate | A Markdown file at the domain root |
| AI Score | Overall readiness to be cited by a model | A 0–100 grade, of which schema coverage is one fifth | One number per scanned site |
The dependency the table cannot express is that schema is the only one of these that fails silently. A missing llms.txt is obvious the moment you fetch the path, and a low AI Score arrives with a list of reasons. A schema block with one malformed property still renders, still deploys, still passes review, and is simply discarded by every parser that reads it. That is why validation is a gate rather than a nice-to-have, and why FAQPage gets its own page here: it is the single type where the visible-parity requirement bites hardest.
In AIRank
AIRank detects schema on every scanned page, grades it for validity and completeness, and ships an auto-injection feature via the WordPress plugin and Shopify app that can generate missing schema from your existing content.
- FAQPage Schema
A specific schema.org type that declares a page contains a list of question-and-answer pairs, optimized for structured ingestion by search and AI systems.
- Generative Engine Optimization
The practice of making a website more likely to be cited by AI answer engines (ChatGPT, Claude, Perplexity, Google AI Mode) rather than simply ranked on a traditional search results page.
- AI Score
AIRank's 0–100 grade for how likely a site is to be cited by a language model, calculated from 47 weighted checks across four pillars.
JSON-LD, Microdata or RDFa — which should I use?
JSON-LD, in almost every case. It lives in one script block that is independent of your markup, so a designer restructuring the page cannot break it, and it is trivial to generate from the same data that renders the page. Microdata and RDFa scatter attributes across your DOM, which means every template change is a chance to silently invalidate the structured data. Google supports all three; only one of them survives a redesign.
Does schema markup still help if the rich result was deprecated?
Yes, and this is the most common misunderstanding. Rich results are a display decision Google makes and revokes — HowTo lost its treatment, and FAQ rich results were restricted to well-known government and health sites in 2023. None of that changed what the markup does for extraction. A language model reading a page still benefits from typed entities and explicit question-answer pairs regardless of whether Google chooses to decorate the listing.
Where should the JSON-LD block go, head or body?
Either works; head is tidier. What matters far more is that the block is present in the server-rendered HTML rather than injected later by client-side JavaScript. Several crawlers that feed AI retrieval layers do not execute scripts, so a block that only exists after hydration effectively does not exist. If your framework renders it at request time, verify by fetching the page with a plain HTTP client and searching the raw response.
How many schema types should one page have?
As many as it genuinely is, and no duplicates. A product page reasonably carries Product, BreadcrumbList and Organization at once, because those describe three different things. What it must not carry is two Product blocks, which is what happens when a theme and a plugin both emit. Add types because the page really contains that entity, not because more markup feels safer — an inaccurate type is worse than a missing one.
My schema validates but nothing changed. What now?
Validation only proves the block parses. Check three things in order: that it is in the server-rendered HTML rather than added by script, that every marked-up property is visible on the page, and that no second block of the same type exists elsewhere in the document. If all three pass, the markup is doing its job and you are looking at the wrong metric — schema improves extraction and eligibility, not ranking position.
Written by
The AIRank Editorial Team
Research & editorial, AIRank
The AIRank editorial team runs the 47-point scanner, the Observer pings, and the GEO research programme every week. Writing is reviewed by the core engineers who build the Injector, Blaster, and Surgeon agents.
Last reviewed
About the team →