Schema Markup Validator

Checks JSON-LD in three layers and says which authority each finding comes from, because valid schema.org and eligible for a rich result are different things.

Enable JavaScript to customise; default output below.

The JSON on its own, or the whole script tag. The default has three real problems in it: a relative image, a numeric price and a non-ISO date.

Live preview schema-report.txt
Parses               yes
Nodes with a @type   2
Types this checks    Product
Types it does not    Offer
Errors               1
Warnings             7

Findings
  error, schema.org  datePublished is "14/03/2026", which is not ISO 8601. Write 2026-03-14, or 2026-03-14T09:30:00+00:00 when the time matters.
  warning, Google    image is "/img/pro.jpg", which is not an absolute URL. A leading slash is not enough: structured data gets read out of the page's context, so there is nothing for it to resolve against.
  warning, Google    price at offers is a number. Google asks for a string, because 9.90 as a number loses the trailing zero and 1,299 with a separator is not a number at all.
  warning, Google    Product has no aggregateRating, which Google recommends. Only mark up ratings that are visible on the page. Invisible review markup is the most common manual action in this area.
  warning, Google    Product has no brand, which Google recommends.
  warning, Google    Product has no description, which Google recommends.
  warning, Google    Product has no review, which Google recommends.
  warning, Google    Product has no sku, which Google recommends.

The errors are the ones that stop a rich result. Each says which
authority it comes from: a schema.org error means the data is malformed,
and a Google error means the rich result needs something the data does
not have.

Offer is a type this tool has no rules for, so it was parsed and not
checked. That is not a verdict either way: schema.org has hundreds of
types and only a dozen or so produce rich results.

Mark up only what a visitor can see. Invisible review markup, prices
that differ from the page, and FAQ content that is not on the page are
the usual causes of a manual action in this area, and a manual action
costs every rich result on the site rather than the one.

This is a static check of one block. Google's Rich Results Test fetches
the live page, runs the JavaScript and tells you which rich result it is
eligible for, which is the thing to confirm with before you ship. Use
this to fix the obvious problems first, because a broken block fails
silently and a fetch tells you nothing about why.

JSON-LD in a script tag is the format to use. Microdata and RDFa are
still read, and they tangle the data into the markup, so a template
change breaks them in ways nobody notices.

Output is valid and updates as you type.

Structured data has three ways to be wrong, and a validator that does not say which one you are looking at is not much help.

The JSON does not parse. A trailing comma, or a smart quote from a word processor. The whole block is ignored in silence: no error anywhere, no rich result, and nothing in the page source that looks wrong.

The vocabulary is wrong. @context is not schema.org, or @type is Blogpost rather than BlogPosting.

It is valid schema.org and not eligible for a rich result. This is the common case. schema.org is permissive: a Product with nothing but a name is valid. Google’s requirements are stricter, and a Product with no offer, review or rating will not produce a rich result however valid it is.

So every finding here says which authority it came from and what it costs.

How to use

  1. Paste the JSON-LD, or the whole <script type="application/ld+json"> block.
  2. Fix the errors first: those are the ones that stop a rich result.
  3. Then confirm with Google’s Rich Results Test, which fetches the live page.

Example

The default sample has three real problems in it:

Parses               yes
Nodes with a @type   2
Types this checks    Product
Types it does not    Offer
Errors               1
Warnings             6

Findings
  error, schema.org  datePublished is "14/03/2026", which is not ISO 8601. Write 2026-03-14, or 2026-03-14T09:30:00+00:00 when the time matters.
  warning, Google    price at offers is a number. Google asks for a string, because 9.90 as a number loses the trailing zero and 1,299 with a separator is not a number at all.
  warning, Google    image is "/img/pro.jpg", which is not an absolute URL. A leading slash is not enough: structured data gets read out of the page's context, so there is nothing for it to resolve against.

Note the Types it does not line. Offer has no rules here, so it was parsed and not checked, and saying so is more useful than a clean report that implies it passed.

Pitfalls

A relative URL is not enough, even with a leading slash. Structured data gets read out of the page’s context, so /img/pro.jpg has nothing to resolve against. Absolute https, every time.

Price is a string. 9.90 as a number loses its trailing zero, and 1,299 with a separator is not a number at all. Google’s documentation asks for a string with priceCurrency beside it.

Author is an object. "author": "Liton" is valid schema.org and will not produce the author in a rich result. It needs to be a Person or an Organization with a name.

Dates are ISO 8601 with an offset. 14/03/2026 is ambiguous between two continents and is not a date to a parser. 2026-03-14 for a day, 2026-03-14T09:30:00+00:00 when the time matters.

Durations are ISO 8601 too. PT30M, not “30 minutes”. This catches out every recipe and video markup written by hand.

Only mark up what a visitor can see. Invisible review markup, a price that differs from the page, FAQ content that is not on the page: these are the usual causes of a manual action, and a manual action costs every rich result on the site rather than the one.

Valid does not mean eligible. Eligibility also needs the page indexed, the markup matching the visible content, and Google choosing to show the result. No validator can tell you the last one.

Google retires rich results. HowTo went in 2023 and FAQ results were cut back to a narrow set of sites. Markup is still worth having for clarity and for other consumers; the rich result is not a promise.

Compatibility

Everything runs in the browser: nothing is uploaded, nothing is stored, and no page is fetched.

Twelve types have rules here: Article with its NewsArticle and BlogPosting aliases, Product, LocalBusiness and its subtypes, Organization, FAQPage, BreadcrumbList, Event, Recipe, JobPosting, WebSite, Person, VideoObject and HowTo. Anything else is parsed, counted and listed as unchecked. schema.org has hundreds of types and only a dozen or so produce rich results, so that is where the rules are.

Required and recommended follow Google’s structured data documentation rather than schema.org’s own cardinality, which is almost entirely optional. Where a message says “schema.org”, it is about the shape of the value: a date that is not a date, a duration that is not a duration, a missing @context.

Recommended properties are only checked on the top-level node. A Person used as an author needs a name and nothing else, and asking it for a jobTitle would bury the findings that matter.

JSON-LD in a script tag is the format to use. Microdata and RDFa are still read and they tangle the data into the markup, so a template change breaks them in ways nobody notices.

Frequently asked questions

Is this the same as Google’s Rich Results Test?
No, and it is not a replacement. That test fetches the live URL, runs the JavaScript and tells you which rich result the page is eligible for. This checks a block you paste, which is faster while you are writing it and works on markup that is not published yet.
What about the Schema Markup Validator at validator.schema.org?
That one checks the vocabulary: whether the types and properties exist and whether the values are the right kind. It deliberately does not check Google’s requirements, which is the half most people actually need.
Why is my valid markup not showing a rich result?
In order of likelihood: the page is not indexed yet, a required property is missing, the markup does not match what the page shows, the site is too new or too small for Google to show it, or the rich result no longer exists for that type.
Should I put the markup in the head or the body?
Either. Google reads JSON-LD wherever it is in the document, and injected by JavaScript too, as long as it is in the rendered DOM.
Can I have more than one block on a page?
Yes, and it is often cleaner: one for the Organization, one for the page’s own type, one for the breadcrumb. They are read together. An @graph with several nodes is the tidier form when they reference each other.
Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.