Docs
schema-customization
Use Zod schema metadata to make the admin UI clearer while enforcing stronger validation. The same schema drives both form rendering and runtime validation.
How the schema controls UI + validation
Every field in your `z.object(...)` is used twice: (1) to render admin form inputs, and (2) to validate submitted data on save.
const DocsConfigPageSchema = z.object({
title: z.string().min(3).max(80),
description: z.string().min(10).max(255),
});Use constraints, not only z.string()
The CMS provides default empty values for generated forms. If you only use `z.string()`, empty strings are valid. Add `.min()` / `.max()` (and other constraints) to enforce meaningful content.
// Weak (allows empty string)
description: z.string()
// Better (recommended)
description: z.string().min(10, "Description is too short").max(255, "Description is too long")
// Optional but constrained when provided
subtitle: z.string().min(5).max(120).optional()describe() adds helper text in admin
Use `.describe(...)` to show contextual helper text under the field in admin forms. This improves editor clarity without changing the final data shape.
summary: z
.string()
.min(20)
.max(280)
.describe("Shown below the page title in previews")meta() customizes field behavior
Use `.meta(...)` to control UI details such as input type, tooltip info, and placeholders.
description: z.string().min(10).max(255).meta({
field: "text-area", // also supports "textarea"
info: "Appears in SEO snippets and social cards",
placeholder: "Write a short, clear description..."
})Supported meta keys
These keys improve admin editing UX while keeping schema-first control.
field: "text" | "textarea" | "text-area" // force input kind
info: string // tooltip beside label
placeholder: string // input/textarea/select placeholderAuto textarea inference
String fields default to input. Some field names are auto-rendered as textarea for convenience (for example `description`, `summary`, `subheading`, `subtitle`, `content`, `body`). Use `meta.field` to override.
// Auto textarea by name (summary)
summary: z.string().min(20).max(280)
// Explicit override if your name is custom
teaserCopy: z.string().min(20).max(280).meta({ field: "textarea" })Optional fields in admin
If a field is optional in Zod, admin labels automatically show `(optional)`. Required fields are treated as mandatory by validation.
badge: z.string().max(40).optional(), // rendered as optional
headline: z.string().min(5).max(100), // requiredComplete recommended example
This pattern gives strong validation and clear editor guidance.
const DocsConfigPageSchema = z.object({
title: z
.string()
.min(5, "Title must be at least 5 characters")
.max(80, "Title must be under 80 characters")
.describe("Main heading shown at the top of the page")
.meta({ placeholder: "Configuration" }),
description: z
.string()
.min(10, "Description must be at least 10 characters")
.max(255, "Description must be under 255 characters")
.describe("Used in previews and intros")
.meta({
field: "text-area",
info: "Keep this concise and useful",
placeholder: "Explain what this page covers..."
}),
steps: z.array(
z.object({
title: z.string().min(3).max(100),
description: z.string().min(10).max(300).optional(),
code: z
.string()
.min(1)
.optional()
.meta({ field: "text-area", placeholder: "Paste code snippet" }),
language: z.string().min(2).max(20).optional()
})
)
});