Learning schema and structured data
Structured data is a few lines of code telling a machine what this page is: a product, an article, a business. Google recommends the JSON-LD format and holds one rule everything else follows from: do not mark up what is not visible on the page.
- Lesson 10 of 15
- Intermediate
- Free, no signup
Four pieces that are incomplete without each other
Structured data works only when all four pieces are in place. The first piece is always the page, not the code.
-
The page
The information a reader sees, before any code
-
The entity
What the page actually is: product, article, business
-
The markup
JSON-LD carrying that type's required properties
-
The result
Becoming eligible for a rich presentation, no more
The fourth piece is not a guarantee. Correct markup makes a page eligible; the decision to display stays with Google.
Last checked: Facts and tool names in this lesson are re-checked against their sources on this date.
What does Google do with this code?
Google writes that it uses the structured data it finds on the web to understand the content of a page, and also to gather information about the web and the world: people, books, companies. So this code is first of all a shared language rather than an SEO technique.
Three formats are supported: JSON-LD, Microdata and RDFa. Google writes that all three are equally fine for it as long as the markup is valid and properly implemented, and in most cases recommends JSON-LD, because it sits in its own script block instead of being interleaved through the page HTML. In practice that is the one people write.
And an expectation to cut short: writing this code does not mean getting a rich result. Correct markup makes a page eligible for a rich presentation, and the decision to show it stays with Google. A page with nothing to say still has nothing to say with schema on it.
One step further: the presentations themselves get withdrawn. In its documentation updates Google wrote that the FAQ rich result would no longer appear in Search from 7 May 2026, and later removed that documentation altogether; it had already done the same for the How-to rich result. So markup that was worth writing in 2023 is the same code today, just as valid, and shows nothing in the results. This site prints FAQPage on pages that have questions and answers, and since that date no rich result comes out of it; leaving it in place is fine, because it describes genuinely visible content, but expecting anything from it is not.
Which schema type belongs to your page?
The answer is short: the type the page actually is. A hosting sales page is a Product. A blog post is an Article. A contact page is an Organization or a LocalBusiness. A service page is a Service. Choosing the type by which rich result looks nicest is the mistake that ends in a manual action later.
Every type has required properties and recommended ones, listed in Google's own documentation for that type. The LocalBusiness type, for instance, has two required properties: address and name. Google writes that you must include all the required properties for an object to be eligible for a rich result.
More than one type on a single page is normal: a blog post is an Article, its trail is a BreadcrumbList, and its publisher is an Organization. The clean way to write several of them is a graph: one JSON-LD block holding several nodes that reference each other by identifier. Then "the organization" is defined once and every other node points at it.
A small example holding all three ideas at once: the right type, a required property, and a reference by identifier.
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://example.com/#org",
"name": "The business name",
"url": "https://example.com/"
},
{
"@type": "Article",
"@id": "https://example.com/post/#article",
"headline": "The same headline printed on the page",
"datePublished": "2026-09-05",
"publisher": { "@id": "https://example.com/#org" }
}
]
}
Three things are visible in those few lines. First, every node has an identifier and that identifier is a URL, so anything else can point at it. Second, the article's publisher is not defined again and merely references the organization's identifier; that one move is what stops two different descriptions of the same thing. Third, the article headline is the headline printed on the page rather than an optimised variant of it, which leads straight into the rule in the next section.
Why you must not mark up what is not on the page
Google has written this in two separate places and is blunt in both. In the general guidelines: "Don't mark up content that is not visible to readers of the page." And on the introduction page: do not create blank pages just to hold structured data, and do not add markup about information the user cannot see, even if that information is accurate.
Those last words are where the argument ends. The most common justification we hear is "but the number really is correct, we just did not print it on the page". Under the policy, that changes nothing.
The consequence has a name: "structured data issue" is one of the manual actions reported in Search Console. Its effect is that the page stops being eligible for rich results. What over-marking was supposed to win is exactly what it loses.
The correct order is simple and always one-way: put the information on the page first, then mark it up. Never the other way round.
What to mark up and what never to mark up
On the page, so mark it up
- The price a visitor can see
- Opening hours printed on the contact page
- A question and answer the user can read
- An author whose name appears under the title
Not on the page, so never
- A rating shown nowhere on the page
- A self-collected rating on a business or organization type
- A question and answer that exists only inside the code
- A type the page is not, because its result looks nicer
The second column is not a matter of taste. "Structured data issue" is one of Google's manual actions.
Why the rating you collected yourself gets no stars
This is where most of the wrong schema in a small market gets written. Google's documentation on review snippets carries one clause that has to be read literally: if the entity being reviewed controls the reviews about itself, its pages using LocalBusiness or any other type of Organization structured data are ineligible for the star review feature.
The example Google gives is exactly our common case: a review about entity A placed on the website of entity A, either directly in its structured data or through an embedded third-party widget. That means your own review box, even when the reviews are genuine.
Note that the restriction is tied to two types and not to all of them. Ratings on types such as Product and Software App are still supported; what Google closed off is self-collected ratings on the business and organization types. For LocalBusiness it says separately that the rating property is only recommended for sites that capture reviews about other businesses.
And one more rule that gets less attention: the review you mark up must be available to a visitor from that same page, and it must be immediately obvious that the page has review content. If you write an aggregate rating into the markup, that same rating has to be visible on the page.
How to test, and how many blocks a page carries
Google names two separate stages and the split matters. During development, the Rich Results Test; after deployment, the rich result status reports in Search Console, because markup usually breaks later, through templating and serving issues, rather than while you write it. The URL Inspection tool is there to see whether Google found your structured data at all. For checking the code itself, without the question of rich-result eligibility, there is schema.org's own validator.
Now the trap the tools do not show you: a page can carry more than one JSON-LD block, and usually does, because the theme prints one and each plugin prints another. On one of our own pages on 5 September 2026 we counted four separate blocks: a five-node graph from the theme, plus three standalone blocks printed by plugins. Count them with one command:
curl -s -A "Mozilla/5.0" "https://example.com/page/" \
| grep -c 'application/ld+json'
Why it matters: when several blocks sit on one page, the same thing can be described twice in two different ways. On that page of ours there are two breadcrumb nodes that disagree, one with two steps and one with three. That is exactly what this check exists to find, and we found it on our own site. The right rule is to keep one source of truth per entity and have everything else reference it. On a site where several plugins print schema at once, that consolidation is usually the first job an SEO project should do.
The testing order, from code to post-deployment
The first two steps come before deployment and the next two after it. Keeping them apart is what Google recommends.
-
1
The schema.org validator
Is the code well formed and do the nodes fit together?
-
2
The Rich Results Test
Does Google know this type, and are required properties present?
-
3
URL Inspection
Did Google see this code in the version it actually fetched?
-
4
The status report in Search Console
Did it break after deployment? This is where that shows
None of these tools says a rich result will be shown; they only say whether something blocks it.
The fast path, with AI
Language models write JSON-LD fluently, and that is exactly what makes them dangerous here: the output always looks valid, even when it carries properties that do not exist on the page. The fast and safe route is to invert the order of work. Ask the model for an evidence table first, then build the code only from the rows that have evidence. A cheap fast model is enough for this, since it is text matching; our current pick is in the <a class="text-link" href="/en/ai/">AI section</a>.
- Extract the visible text of the page with the command in the recipe. Do not paste the full page code; on a site that inlines its CSS even the head section is too big for a model.
- Decide the schema type yourself and copy that type's required and recommended property list out of Google's documentation. Choosing the type is not the model's job.
- Run the first stage of the recipe: the evidence table. One row per property, with the sentence from the page itself that supports the value, or an explicit "not on the page".
- Delete the "not on the page" rows, then run the second stage so the code is built from what remains. Finally parse the output once and put it through the Rich Results Test.
Copy-ready recipe
U="https://example.com/page/"
curl -s -A "Mozilla/5.0" "$U" > p.html
python3 -c '
import re, html, sys
h = open("p.html", encoding="utf-8", errors="replace").read()
m = re.search(r"<main.*?</main>", h, re.S)
b = m.group(0) if m else h
b = re.sub(r"<(script|style|nav|footer)[^>]*>.*?</\1>", "", b, flags=re.S)
t = html.unescape(re.sub(r"<[^>]+>", " ", b))
print(re.sub(r"[ \t]+", " ", t).strip())'
# count the blocks already there, and parse the model output:
grep -c 'application/ld+json' p.html
python3 -c 'import json,sys; json.load(open("out.json")); print("JSON OK")'
---- Stage 1: the evidence table ----
Role: structured data reviewer. Write no code at this stage.
Schema type: {type}
The required and recommended properties of this type, from Google's docs:
{property list}
The visible text of the page:
{output of the command above}
Write one row per property with three columns: property name, value, evidence.
The evidence column must be a sentence quoted verbatim from the text above that
supports the value. If there is no such sentence, write "not on the page" in
both the value and the evidence column.
Rules:
- Fill no value from your own general knowledge, even when you are sure it is
correct.
- Write units, currency and dates exactly as they appear on the page, and
convert nothing.
- Add no property that is not in the list above.
- Write no JSON at this stage.
---- Stage 2: building the code ----
Now build one JSON-LD block from the rows that have evidence, and only those.
Rules:
- Drop the "not on the page" rows entirely. Do not substitute anything for them.
- If a required property of this type has no evidence, do not build the code;
state which required property is missing from the page instead.
- Output JSON only, with no commentary and no surrounding text.
Before you trust the output: Check two things yourself before publishing. First, that the sentences in the evidence column really are on the page; if the model composed one, the evidence table becomes precisely the thing it was meant to prevent. Second, that the page does not already carry another block describing the same entity differently; the counting command is in the recipe for that. And put the generated code through the Rich Results Test: JSON parsing only tells you the syntax is right, not that the meaning matches the page.
AI in this kind of work
Schema is one of the few parts of SEO where a language model genuinely is faster than a person, because the output has a fixed structure and the input is text. The problem sits elsewhere: when a model does not know something it invents it, and the invention looks exactly like the rest of the code.
Tools that actually help
- Claude Suits the evidence-table stage, because it paraphrases less when you ask for verbatim quotes. Iran is on neither of Anthropic's two supported-countries lists.
- Gemini Enough for producing the JSON block itself from an approved table. Our own entry notes Iran is not on Google's supported-countries list; the payment route is in <a class="text-link" href="/en/ai/buy/">buying AI access</a>.
- Rich Results Test Not AI, and it replaces none of the above. It is the only place that tells you Google recognises this type and no required property is missing.
Where it backfires
The specific risk here is simple and mechanical: the model writes properties that do not exist on the page, because it learned from similar files that those properties are usually present. A rating of 4.8 that appears nowhere, a review count nobody counted, a publication date that was never printed. Google's guidance is explicit that you must not mark up content that is not visible to readers, even when it is accurate, and "structured data issue" is one of the manual actions. The second layer of risk is properties that do not exist at all or are not defined for that type; the tools catch those, but they still cost you time. The rule we hold to, and it is our agency rule: mark up only what is visible on the page.
Sources: Google Search Central: structured data general guidelines Google Search Central: introduction to structured data markup Google Search Console Help: manual actions report
Where this advice stops
Structured data does not bring rankings. What it does is make a page eligible for a rich presentation and explain to a machine what the page is about; Google has nowhere said the code is itself a ranking signal. If a page has the wrong content, schema does not fix it. Nor can a rich result be guaranteed: the decision to display stays with Google and can differ for the same page from one week to the next.
From our own work
On this very site the SEO plugin's default schema is switched off with a single filter line, and the theme prints its own JSON-LD graph, so every node is built in one place and stays consistent. The second decision is more interesting: an aggregate rating is injected only on the eight pages that genuinely carry a qualifying page-level node, the seven hosting and SSL pages that have a Product and the one tool page that has a SoftwareApplication. On the service, about and blog pages the stars are visible inside the page but no rating markup is produced at all, because Google's rule on self-collected reviews forbids it. The three domain pages are off the list because they have no Product node at all, and four further tool pages are off it because they only inherit SoftwareApplication from the organization catalog, which is a catalog item and not that page's item. One last condition in the code ties it together: if the review count is zero, no rating is produced, because a rating schema with no reviews is a Search Console error in its own right. That is why the Product node on the Linux hosting page, on the day this lesson was written, carries a price and no rating. Open it and see for yourself.
Real follow-up questions
Does schema raise a site's ranking?
Google says it uses structured data to understand the content of a page and to make the page eligible for a rich presentation. It has nowhere said the code itself is a ranking signal. The effect people see in practice usually comes from click-through rate rather than from movement in the results.
Should I write schema with a plugin or by hand?
For most sites a plugin is enough and is the right answer. Writing it by hand starts to make sense when several plugins print schema at once and the descriptions disagree, or when the page is a type the plugin does not know. If you do write it by hand, count the existing output first so you do not create two parallel descriptions.
I have genuine customer reviews, so why no stars?
Because Google's rule is not about whether the review is genuine, it is about who controls the reviews. A review about yourself, collected on your own site, is not eligible for stars on the business or organization types. Keep the reviews, since they are worth having for the visitor; just do not build rating markup on those two types.
Now that the FAQ rich result is gone, should we remove FAQPage?
You do not have to. The code is still valid and describes content that really is on the page, so it carries no penalty and no error. It simply shows nothing in the results any more, and if the only reason it was written was that display, removing it costs nothing either.