@krantho/schema
Define typed content collections with Zod schemas, with validation and type inference.
Define your content structure in code. Collections declare their fields with Zod schemas, which gives you TypeScript type inference and validation: the API validates every document it creates, updates or imports, and validateAllContent() checks your markdown files. The same schemas are the forms editors fill in the admin.
Install#
pnpm add @krantho/schemaPeer dependency: zod 4.
Define a collection#
defineCollection declares a content type with its fields and its source.
import { defineCollection } from '@krantho/schema'
import { z } from 'zod'
export const pages = defineCollection({
name: 'pages',
source: 'filesystem',
directory: 'content/pages',
schema: z.object({
title: z.string(),
description: z.string(),
'meta-description': z.string().optional(),
status: z.enum(['draft', 'published', 'archived']).default('published'),
}),
})source: 'filesystem': the markdown files are the source of truthsource: 'database': the database is the source of truthdirectoryis required for filesystem collectionspath(optional) is where the documents render as pages, read by the sitemap, the draft preview, the re-render after a publish and the page's JSON-LD: a pattern such as'/deploy/:slug'(the documentindexrenders at/deploy), a fixed path such as'/'for a landing page, orfalsefor documents with no page of their own. Without it,pagesrender at the site root (homeat/),globalshave no pages, and other collections render at/<name>/<slug>— see Content model
Pass collections to the server#
Collections are wiring, not configuration. Hand them to the server handler next to the configuration from krantho.config.ts:
createKranthoHandler({ config, collections: { pages, blog } })The handler assembles them with defineConfig(). Call defineConfig() yourself only when you need a ContentConfig outside the server, such as for validateAllContent() below:
import { defineConfig } from '@krantho/schema'
const contentConfig = defineConfig({ collections: { pages, blog } })Validate content#
Check the markdown files of every filesystem collection against its schema. Database collections are validated when they are written.
import { validateAllContent } from '@krantho/schema'
const result = await validateAllContent(contentConfig, adapter, { mode: 'warn' })
if (!result.valid) {
for (const err of result.errors) {
console.error(`${err.collection}/${err.slug}: ${err.field}: ${err.message}`)
}
}mode: 'strict' (the default) throws a ContentValidationError at the first document with errors. mode: 'warn' collects every error and returns them with a summary (total, valid, invalid).
Validate a single item:
import { validateFields } from '@krantho/schema'
const errors = validateFields(
{ title: 'Home', description: 'Welcome' },
pages,
'home',
)Page composition#
Declare the content a page route depends on.
import { definePage, slug, sections, collection } from '@krantho/schema'
const aboutPage = definePage({
route: '/about',
content: {
page: slug('about'),
story: sections('about', ['Why it exists', 'Where it stands']),
releases: collection('releases', { status: 'published', limit: 3 }),
},
})slug(name): a single content filesections(slug, names): specific## sectionsof a filecollection(name, options): a filtered collection
Runtime schema#
The admin can add fields to collections without code changes. Runtime fields are additive only, and code-defined fields always take precedence.
import { mergeSchemas } from '@krantho/schema'
const merged = mergeSchemas(pages.schema, runtimeFields)Export the merged schema as Zod code to adopt runtime fields into your codebase:
import { exportSchemaAsZod } from '@krantho/schema'
const code = exportSchemaAsZod('pages', codeProps, codeRequired, runtimeFields)Site configuration#
Site identity is not validated here. The site section of krantho.config.ts is part of the configuration tree in @krantho/config, and resolveConfig() validates the whole tree once at startup. See Configuration.
JSON Schema export#
Export a collection schema as JSON Schema, which the admin uses to generate its forms.
import { collectionToJsonSchema } from '@krantho/schema'
const jsonSchema = collectionToJsonSchema(pages)Next steps#
- Load content with your schemas: @krantho/core
- Set up the server handler: @krantho/server
- Manage schemas in the admin: @krantho/admin