@ifthen/schema-org
v1.5.2
Published
Schema.org JSON-LD builder functions
Maintainers
Keywords
Readme
@ifthen/schema-org
Typed Schema.org JSON-LD builders.
Exports pure TypeScript builder functions and a single React renderer. Consumers never need to import from schema-dts directly.
Quick start
npm install @ifthen/schema-orgPublished publicly to the npm registry — no auth required to install.
import {
buildWebPage,
buildWebSite,
buildOrganization,
buildTouristDestination,
JsonLd,
buildSchemaId,
type SchemaItem,
} from "@ifthen/schema-org";
const baseUrl = "https://www.example.com";
const websiteId = buildSchemaId(baseUrl, "website");
const organizationId = buildSchemaId(baseUrl, "organization");
// inside a React Server Component:
const items: SchemaItem[] = [
buildWebPage({
url: "https://www.example.com/destinations/europe",
name: "Europe",
websiteId,
organizationId,
}),
buildTouristDestination({
url: "https://www.example.com/destinations/europe",
name: "Europe",
containsPlace: [{ url: "https://www.example.com/destinations/france" }],
}),
];
<JsonLd items={items} />;JsonLd handles all cases from a single items prop:
| items length | output |
| ------------ | -------------------------------------------- |
| 0 | renders nothing |
| 1 | { "@context": "...", "@type": "...", ... } |
| 2+ | { "@context": "...", "@graph": [...] } |
Always pass all entities for a page together — adding a new entity is a safe items.push(...), not a second <script> tag.
Available builders
All builders and their prop types are exported from the package root (src/index.ts):
| Builder | Schema.org type |
| ------------------------- | --------------------------------------------------------------------- |
| buildWebPage | WebPage, CollectionPage, ContactPage, or any subtype via type |
| buildWebSite | WebSite |
| buildOrganization | Organization |
| buildTouristDestination | TouristDestination |
| buildItemPage | ItemPage |
| buildCollectionPage | CollectionPage |
| buildTouristTrip | TouristTrip |
| buildCity | City |
| buildOffer | Offer |
| buildAggregateOffer | AggregateOffer |
| buildAccommodation | Accommodation |
| buildFAQPage | FAQPage |
| buildItinerary | ItemList (of itinerary items) |
| buildBreadcrumbList | BreadcrumbList |
| buildPerson | Person |
| buildCourse | Course |
| buildArticle | Article, BlogPosting, NewsArticle, or any subtype via type |
| buildPeriodical | Periodical |
| buildPublicationIssue | PublicationIssue |
| buildPublicationVolume | PublicationVolume |
| buildEvent | Event, or any subtype via type |
| buildBlog | Blog |
| buildPodcastSeries | PodcastSeries |
| buildPodcastEpisode | PodcastEpisode |
Plus the buildSchemaId helper and the JsonLd renderer shown above.
buildItinerary composes buildItineraryItem internally from a plain itineraryItems prop — buildItineraryItem itself is not re-exported from src/index.ts and should be treated as internal.
@id and url behavior
Builders that take a url build @id as ${url}/#${fragment}, with any trailing slash stripped from url.
Fragment resolution order:
idFragmentprop (explicit override)- internal
TYPE_FRAGMENTSmap if exists (seesrc/builders/webPage.tsfor example) type.toLowerCase()fallback
This lets new subtypes work without package changes unless a subtype has a non-obvious canonical fragment. Types listed in a builder's TYPE_FRAGMENTS map use kebab-case fragments, e.g. #faq-page, #item-page, #blog-posting; any other type is just lowercased, e.g. AboutPage → #aboutpage.
Exceptions:
buildArticleandbuildWebPageaccept an arraytype, which is emitted as-is in@type; the fragment comes from the first entry. An empty array falls back to the default type (Article/WebPage) for both@typeand the fragment. An empty-stringidFragmentalso falls back to the type-based fragment.buildWebPageonly emitsmedicalAudiencewhentypeis or containsMedicalWebPage.buildItemPagewithdisableFragment: trueuses the cleanedurlas@id.buildPersonhas an optionalurland omits@id/urlwhen it's absent.buildPeriodical,buildPublicationIssue,buildPublicationVolume,buildPodcastSeries,buildBlog,buildOrganizationandbuildWebSitetake a fullidprop instead.buildCitycurrently emits a fragment-only@id(e.g.#city).
First-time setup to work on this repo
nvm use
npm installnpm install runs the prepare script (tsc -p tsconfig.build.json), which builds dist/.
Add a new builder
- Create
src/builders/<entityName>.ts - Export the builder function and props interface
- Re-export from
src/index.ts - Add tests in
src/builders/<entityName>.test.ts - Build (
npm run build)
Verify changes
npm test
# or with coverage
npm run test:coverageTo validate structured data output, paste the JSON-LD from the page source into the Schema Markup Validator — it correctly handles @graph:
https://validator.schema.orgNote: sdtt does not support @graph and will report zero schema.org entities even when the output is correct.
Publishing
.github/workflows/publish-to-npm.yml publishes to the public npm registry automatically whenever package.json's version changes on main (or via manual workflow_dispatch).
- Bump
versioninpackage.json - Merge/push to
main— the workflow diffs the local version against what's currently live on the registry and publishes only if they differ
For breaking changes to exported builder prop interfaces or output contracts, bump the minor or major version accordingly.
