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 placeholder

Auto 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),  // required

Complete 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()
    })
  )
});

Need details on roles, categories, and sidebar icons?