When Google retired the Structured Data Testing Tool and replaced it with the rate-limited Rich Results Test, the fast feedback loop for fixing JSON-LD got worse. The Rich Results Test renders your live page, which is great for a final check but slow when you are iterating on a snippet and just want to know which property you forgot.
So I built a small, dependency-free validator that runs entirely in the browser: paste a JSON-LD snippet or a whole HTML page, and it tells you, per object, what is missing. No signup, no rate limit, nothing uploaded.
Tool: https://aitoolsinsiderhq.com/schema-validator.html
The part that actually trips people up
Schema validity has two layers, and conflating them is where most "my rich result won't show" bugs come from:
-
Is it valid JSON-LD? (parses, has
@contextand@type) - Does it have the properties Google needs for that rich result?
Your markup can pass layer 1 and still be invisible in search because it fails layer 2. A Product that parses perfectly but has no offers, review, or aggregateRating is not eligible for a product rich result, full stop.
The validation model
The core is a per-type ruleset of required (error) vs recommended (warning) properties, mirroring Google's structured-data docs:
const RULES = {
Article: { req: ["headline"],
rec: ["author","datePublished","dateModified","image","publisher"] },
FAQPage: { req: ["mainEntity"], rec: [] },
Product: { req: ["name"],
rec: ["image","description","brand","offers","aggregateRating","review"] },
// ...Organization, BreadcrumbList, HowTo, Review, Recipe, Event, VideoObject...
};
Two implementation details that matter more than they look:
Walk the whole tree, including @graph. Real pages rarely ship one flat object. They ship a @graph array, with nested Offer, AggregateRating, ListItem, and Question objects. If you only check the top-level @type you miss most of the document. A recursive collector that descends into @graph and into any nested object with its own @type catches them all.
"Present" is not the same as "truthy-and-non-empty". "image": "" or "mainEntity": [] will pass a naive prop in obj check and then fail in the wild. The check has to reject empty strings and empty arrays:
function has(o, prop) {
if (!(prop in o)) return false;
const v = o[prop];
if (v == null) return false;
if (typeof v === "string" && v.trim() === "") return false;
if (Array.isArray(v) && v.length === 0) return false;
return true;
}
Conditional requirements need special cases. Product is the classic: it needs at least one of offers, review, or aggregateRating. A flat required-list cannot express "one of these three", so that rule gets its own branch.
Things I learned wiring this onto a real site
- A surprising number of FAQPage blocks have a
mainEntityarray where individualQuestionobjects are missingacceptedAnswer, so they silently drop out of the FAQ result. -
BreadcrumbListitems needpositionandname; people often shipitemURLs with noname. - Smart quotes from a CMS are the number one cause of "valid in my editor, invalid in production" JSON-LD. The parser error message is the fastest way to spot them.
It is client-side and free, so if it is useful, use it on your unpublished drafts too. And if you find a type or property check that is wrong or missing, I would genuinely like to know.
I write about AI tools and AI-search visibility at AI Tools Insider, where this validator and a few other free no-signup tools live.
Top comments (0)