Custom Fields
Custom Fields
Yamblog uses Zod for frontmatter validation. The default schema covers common fields (title, date, author, tags, etc.), but you can extend it with any fields your project needs.
How it works
Pass a custom schema to defineBlog. It replaces the default schema entirely, so start by extending defaultSchema rather than rebuilding it from scratch.
import { defineBlog, defaultSchema } from '@yamblog/core';import { z } from 'zod';
const blogSchema = defaultSchema.extend({ authorWebpage: z.string().url().optional(),});
export const blog = defineBlog({ contentDir: 'content/posts', schema: blogSchema,});Any post that sets authorWebpage in frontmatter will be validated as a URL. Posts without it pass validation because the field is optional.
Validation types
Optional string
const blogSchema = defaultSchema.extend({ authorWebpage: z.string().url().optional(), subtitle: z.string().optional(),});Required string
const blogSchema = defaultSchema.extend({ // build will fail if this field is missing from any post canonical: z.string().url(),});Enum
const blogSchema = defaultSchema.extend({ status: z.enum(['draft', 'review', 'published']).default('draft'),});Regex-validated string
const blogSchema = defaultSchema.extend({ version: z.string().regex(/^\d+\.\d+\.\d+$/, 'must be semver'),});Array of strings
const blogSchema = defaultSchema.extend({ coAuthors: z.array(z.string()).default([]),});Number with range constraint
const blogSchema = defaultSchema.extend({ difficulty: z.number().int().min(1).max(5).optional(),});TypeScript types
defineBlog is generic — when you pass a custom schema, blog.getPosts() returns Post<typeof yourSchema>[] automatically. No manual type declarations or casts needed.
const posts = await blog.getPosts();posts[0].authorWebpage; // typed as string | undefined ✓posts[0].nonExistent; // TypeScript error ✓Full example — author webpage field
lib/blog.ts
import { defineBlog, defaultSchema } from '@yamblog/core';import { z } from 'zod';
export const blogSchema = defaultSchema.extend({ authorWebpage: z.string().url().optional(),});
export const blog = defineBlog({ contentDir: 'content/posts', schema: blogSchema,});content/posts/my-post.md
---title: "My Post"date: "2026-05-01"author: "Ada Lovelace"authorWebpage: "https://example.com/ada"tags: []draft: false---
Post body here.AuthorCard.astro (renders name + optional link)
---interface Props { name: string; webpage?: string;}const { name, webpage } = Astro.props;---
<div class="author-card"> {webpage ? <a href={webpage} target="_blank" rel="noopener noreferrer">{name}</a> : <span>{name}</span> }</div>pages/blog/[slug].astro
---import { blog } from '../../lib/blog';import AuthorCard from '../../components/AuthorCard.astro';
export async function getStaticPaths() { const posts = await blog.getPosts(); return posts.map(post => ({ params: { slug: post.slug }, props: { post } }));}
// post is Post<typeof blogSchema> — authorWebpage is typed automaticallyconst { post } = Astro.props;---
<AuthorCard name={post.author} webpage={post.authorWebpage} />Enforcing types at build time
TypeScript knows about your custom fields, but type errors are only caught if your build actually runs a type check. This varies by platform:
Next.js
next build runs tsc — type errors in pages and components are caught automatically. No extra setup needed.
Astro
Astro’s bundler transpiles TypeScript but doesn’t type-check .astro templates. Add astro check before astro build:
"build": "astro check && astro build"React / Vite
Add tsc --noEmit before bundling:
"build": "tsc --noEmit && vite build"Validation at build time
Run blog.validateContent() during your build step to catch missing required fields early:
const posts = await blog.validateContent();// throws if any post fails schema validationThis is especially useful in CI — add it as a pre-build check so broken frontmatter never reaches production.