Fast, zero-dependency JSON-LD validator for schema.org structured data.
There's no good package for this. Existing options are either too loose (just check JSON is valid) or too complex (full JSON-LD spec compliance that nobody needs for SEO). This is the practical middle ground — validate what Google actually requires for rich results, fast, zero dependencies, clear output.
Tested against real production sites. The bugs it catches are real.
npm install @probeo/jsonldPass raw HTML and get results for all JSON-LD blocks on the page:
import { validateHtml } from "@probeo/jsonld";
const result = validateHtml(html);
console.log(result.blocks); // 2
console.log(result.errors); // 3
console.log(result.warnings); // 5
console.log(result.results); // per-block resultsimport { validateObject } from "@probeo/jsonld";
const result = validateObject({
"@context": "https://schema.org",
"@type": "Article",
"headline": "My Article",
"author": { "@type": "Person", "name": "Jane Doe" },
"datePublished": "2024-01-15",
});
console.log(result.valid); // true
console.log(result.issues); // warnings for missing recommended fieldsimport { validateHtml, getIssues } from "@probeo/jsonld";
const result = validateHtml(html);
for (const issue of getIssues(result)) {
console.log(`[${issue.severity}] ${issue.path}: ${issue.message}`);
}
// [error] $.headline: Missing required property "headline" for Article
// [warning] $.image: Missing recommended property "image" for Articleinterface ValidationResult {
blocks: number; // JSON-LD blocks found on the page
errors: number; // total errors (block is invalid, Google won't use it)
warnings: number; // total warnings (reduces quality, may miss rich results)
results: BlockResult[];
}
interface BlockResult {
index: number; // nth block on the page (0-indexed)
type?: string | string[]; // @type value(s)
valid: boolean; // false if any errors present
issues: ValidationIssue[];
}
interface ValidationIssue {
path: string; // dot notation — "$.headline", "$.offers[0].price"
message: string;
severity: "error" | "warning";
type?: string; // the schema type that raised this issue
}Errors mean Google will not use the structured data (missing required fields, invalid JSON, missing @context).
Warnings mean the data is valid but incomplete — missing recommended fields, non-URL values in URL fields, non-ISO dates.
@contextpresent and references schema.org (http://andhttps://both accepted)@typepresent and is a recognized schema.org type@graphblocks — each node validated individually- Suspicious plugin/theme types flagged (
SEO,Tapita,Booster, etc.)
Per-type required and recommended properties matching Google's rich results documentation. Missing required fields are errors. Missing recommended fields are warnings.
- URL fields (
url,image,logo, etc.) — must look like a URL - Date fields (
datePublished,uploadDate, etc.) — must be ISO 8601 - Number fields (
price,ratingValue, etc.) — must be positive numbers - Protocol-relative URLs (
//cdn.example.com) accepted as valid
Product.offers→OfferorAggregateOfferBreadcrumbList.itemListElement→ListItemFAQPage.mainEntity→QuestionQuestion.acceptedAnswer→AnswerHowTo.step→HowToSteporHowToSectionEvent.location→PlaceorVirtualLocationReview.reviewRating→Rating- And more — see schema type definitions
Covers every schema.org type Google uses for rich results:
| Type | Rich Result Feature |
|---|---|
Article, BlogPosting, NewsArticle |
Article rich results |
Product, Offer, AggregateOffer |
Product rich results |
Organization, LocalBusiness |
Knowledge panel, local pack |
Person |
Knowledge panel |
WebSite, WebPage, AboutPage, ContactPage |
Sitelinks search, breadcrumbs |
FAQPage, Question |
FAQ rich results |
BreadcrumbList, ListItem |
Breadcrumb trail |
HowTo, HowToStep |
How-to rich results |
Event |
Event rich results |
Review, AggregateRating |
Review snippets |
VideoObject, ImageObject |
Video, image rich results |
JobPosting |
Job posting rich results |
Recipe |
Recipe rich results |
SoftwareApplication |
App rich results |
Course |
Course rich results |
Service |
Service rich results |
Unknown types produce a warning but not an error — the block is still considered valid.
Validated against real production sites. A few things this catches that simpler validators miss:
- Epoch date defaults — CMS systems often default
datePublishedto0001-01-01T00:00:00. Flagged as a bad date format. - ratingCount vs reviewCount — Google accepts both. Many sites use
ratingCount. Both pass. - Relative vs absolute URLs —
/images/photo.jpgfails the URL check.//cdn.example.com/photo.jpgpasses (protocol-relative is valid). - Nested type mismatches —
Product.offerscontaining aServiceinstead of anOfferis flagged. - Missing priceCurrency — Required on
Offereven when price is0. - Plugin schema spam — Types like
TapitaSEOApp,SeoBoosterflagged as suspicious.
If you're currently validating JSON-LD by hand or with a one-off regex:
// Before
const scripts = html.match(/<script type="application\/ld\+json">(.*?)<\/script>/gs);
const json = JSON.parse(scripts[0].replace(/<[^>]+>/g, ''));
if (!json['@type'] || !json.name) throw new Error('invalid schema');
// After
import { validateHtml } from "@probeo/jsonld";
const result = validateHtml(html);
if (result.errors > 0) {
console.log(getIssues(result).filter(i => i.severity === 'error'));
}| Package | Description |
|---|---|
| fast-a11y | Zero-DOM accessibility checker with axe-core compatible output |
| @probeo/anymodel | Unified LLM router, 11+ providers |
MIT