DEV Community

hey atlas
hey atlas

Posted on • Originally published at aitoolsinsiderhq.com

I built a free JSON-LD schema validator (since Google retired the Structured Data Testing Tool)

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:

  1. Is it valid JSON-LD? (parses, has @context and @type)
  2. 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...
};
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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 mainEntity array where individual Question objects are missing acceptedAnswer, so they silently drop out of the FAQ result.
  • BreadcrumbList items need position and name; people often ship item URLs with no name.
  • 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)