npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

structured-data-kit

v0.1.3

Published

TypeScript toolkit for producing and validating correct schema.org JSON-LD: typed builders, an ajv-backed validator, an HTML extractor, a CLI, and a GitHub Action.

Downloads

560

Readme

structured-data-kit

Valid, typed schema.org JSON-LD — and a way to catch regressions before they cost you rich results.

Structured data drives rich results, but it regresses silently: a renamed CMS field, an optional object emitted as null, a rating with zero reviews, or JSON-LD that describes data not actually on the page. structured-data-kit is a focused TypeScript toolkit that makes those mistakes hard:

  • Typed builders for the common types (Product, Article/BlogPosting, BreadcrumbList, Organization, FAQPage) that construct valid JSON-LD and enforce the tricky inclusion rules at the API level — so you can't accidentally emit an invalid aggregateRating or a null field.
  • A validator (ajv + bundled JSON Schemas) that checks an object is valid JSON-LD and a valid schema.org shape — required fields, enum URLs, ISO dates, @context/@type.
  • An extractor, CLI, and GitHub Action that pull every <script type="application/ld+json"> out of rendered HTML (a file or a live URL) and validate it — so structured data can be checked in CI against real output.

It's a standalone library with no external services. Tested in CI from the first commit.


⚠️ Structured data must match the visible page

These builders make valid JSON-LD. They cannot make it truthful — that's on you. Feeding them data your page doesn't actually show is a Google policy violation ("don't cloak"), not just a quality issue. Always build from the same data object the page renders, and only mark up content a user can see. This kit refuses to invent defaults precisely so it never fabricates fields for you.


For developers: build JSON-LD

npm install structured-data-kit
import { buildProduct, Availability } from "structured-data-kit";

const product = buildProduct({
  name: "Aeropress Go Travel Coffee Press",
  image: "https://example.com/img/aeropress-go.jpg",
  brand: "Aeropress",
  sku: "AP-GO-001",
  offer: {
    price: 39.95,
    priceCurrency: "USD",
    availability: Availability.InStock, // -> "https://schema.org/InStock"
    url: "https://example.com/brewers/aeropress-go",
  },
  aggregateRating: { ratingValue: 4.8, reviewCount: 312 },
});

// product is a schema-dts-typed WithContext<Product>

Emitting in React/JSX

JSX text escaping corrupts JSON, so emit JSON-LD via dangerouslySetInnerHTML (serializing the same object your page renders from):

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{ __html: JSON.stringify(product) }}
/>

The library is framework-agnostic; this is just the correct way to put a JSON string into a <script> from JSX.

Supported types & inclusion rules

| Builder | @type | Required input | | --- | --- | --- | | buildProduct | Product | name, image, offer (price, priceCurrency, availability) | | buildArticle / buildBlogPosting / buildNewsArticle | Article / BlogPosting / NewsArticle | headline, image, datePublished, author | | buildBreadcrumbList | BreadcrumbList | items[] (name, optional item) | | buildOrganization | Organization | name, url, logo | | buildFAQPage | FAQPage | items[] (question, answer) | | buildRecipe | Recipe | name, image, recipeIngredient[], recipeInstructions[] | | buildEvent | Event | name, startDate, location (Place or virtual { url }) | | buildVideoObject | VideoObject | name, description, thumbnailUrl, uploadDate |

The rules the builders (and validator) enforce — the ones that quietly cost rich results:

  • aggregateRating only with real reviews. It is included only when reviewCount > 0 and ratingValue > 0. Emitting reviewCount: 0 is a Rich Results violation, so the Product builder omits the rating otherwise.
  • Omit optionals; never null or "". Absent optional fields are dropped entirely (the builders never set keys to null, and JSON.stringify drops undefined). No empty brand, no null image.
  • Enumerations are schema.org URLs. availability is https://schema.org/InStock, never the bare string "InStock". Use the exported Availability, EventStatus, and EventAttendanceMode constants.
  • Durations are ISO 8601. Recipe times (prepTime, cookTime, totalTime) and video duration are durations like PT20M — the validator enforces the format.
  • Dates are ISO 8601. datePublished, dateModified, etc.
  • @context is exactly https://schema.org and every node has a correct @type.
  • BreadcrumbList positions are 1-indexed, assigned automatically.

Note: the FAQ rich result is deprecated

Google deprecated the FAQ rich result (announced 2026-05-15); FAQ structured data no longer produces an enhanced result for the vast majority of sites. buildFAQPage and the FAQPage schema are retained because the markup is still valid schema.org, but validate() emits a warning for FAQPage nodes so you're not surprised when the rich result doesn't appear.

For CI: validate real output

Both the CLI and the Action extract every JSON-LD block from rendered HTML (handling multiple <script> tags and @graph arrays), validate each, and fail on any invalid or unparseable block.

CLI

# Validate a file or a live URL (exits non-zero on any invalid block)
npx sdk validate examples/product-page.html
npx sdk validate https://example.com/product/123

# Build an object from an input JSON file
npx sdk build product --input data.json
npx sdk build product -i data.json -o product.jsonld

# Just extract the JSON-LD nodes (no validation)
npx sdk extract examples/product-page.html

validate prints a per-node summary (/, plus warnings) and sets the exit code; add --json for a machine-readable report.

GitHub Action

# .github/workflows/structured-data.yml
name: structured-data
on: [push, pull_request]
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: your-org/structured-data-kit/action@v1
        with:
          files: |
            dist/product.html
            dist/article.html
          # urls: https://staging.example.com/product/123
          fail-on-invalid: "true" # default

See action/README.md for inputs and outputs.

Validate programmatically

import { validate, extract, checkHtml } from "structured-data-kit";

validate(product); // { valid, type, errors[], warnings[] }

// Pull and validate everything from a page's HTML in one call:
const report = checkHtml(htmlString);
if (!report.valid) {
  /* report.parseErrors + report.nodes[].result.errors */
}

// Or just the raw nodes:
const { nodes, errors } = extract(htmlString);

Development

npm install
npm run typecheck      # tsc --noEmit
npm test               # vitest
npm run lint           # eslint
npm run build          # bundle the library + CLI, emit .d.ts
npm run build:action   # bundle the GitHub Action into action/dist
npx tsx examples/build-product.ts

Contributing

See CONTRIBUTING.md. In short: add a builder + its JSON Schema

  • tests together, keep the inclusion rules airtight, and never fabricate fields.

License

MIT.