Skip to content

Custom Schemas

When your project requires Schema.org types not included in the built-in catalog (such as PodcastEpisode, MedicalWebPage, or TechArticle), use defineSchema() to construct your own custom builders with identical validation and @graph capabilities.


Use defineSchema() by providing the Schema.org @type name and a Zod schema:

src/lib/schemas/podcast.ts
import { defineSchema } from '@unschema-graph/core';
import { z } from 'zod';
export const PodcastEpisode = defineSchema(
'PodcastEpisode',
z.object({
name: z.string().min(1),
url: z.string().url(),
duration: z.string().optional(),
partOfSeries: z.string().optional(),
})
);

You can now use your custom builder exactly like any built-in builder:

src/pages/podcast/[slug].astro
import { PodcastEpisode } from '../../lib/schemas/podcast';
const episode = PodcastEpisode({
'@id': '#episode-42',
name: 'Building with Astro & unschema-graph',
url: 'https://example.com/podcast/episode-42',
duration: 'PT45M',
partOfSeries: '#podcast-series',
});

Every custom builder automatically:

  • Injects the Schema.org @type attribute ("PodcastEpisode").
  • Accepts an optional @id property.
  • Rejects unknown top-level properties to prevent typos.
  • Obeys global and per-call onError severity modes (throw, warn, silent).
  • Exposes .schema, .entityType, and .safeParse().

Every built-in schema is exported with the *Schema suffix (e.g. PersonSchema, OrganizationSchema, ImageObjectSchema, IsoDateSchema). You can nest them directly into your custom schemas:

src/lib/schemas/podcast-series.ts
import {
defineSchema,
PersonSchema,
ImageObjectSchema,
} from '@unschema-graph/core';
import { z } from 'zod';
export const PodcastSeries = defineSchema(
'PodcastSeries',
z.object({
name: z.string(),
description: z.string(),
author: PersonSchema,
image: ImageObjectSchema.optional(),
})
);