Block Schema & Field Types

All field types, the defineBlock + fields builder, field options, repeaters, and select options.

March 20, 2026

All Field Types

Define a block's editable props with the fields.*() builder inside defineBlock. Each builder returns a field definition; the key you give it in props becomes the content key.

BuilderDescriptionValue Type
fields.text()Single-line text inputstring
fields.textarea()Multi-line text areastring
fields.richText()WYSIWYG editor (Tiptap)string (HTML)
fields.markdown()Markdown editorstring (Markdown)
fields.number()Number inputnumber
fields.date()Date pickerstring (ISO date)
fields.datetime()Date & time pickerstring (ISO)
fields.boolean()Toggle switchboolean
fields.color()Color pickerstring (hex)
fields.media()Image/video uploadstring or string[]
fields.link()Internal/external linkstring
fields.url()URL inputstring
fields.email()Email inputstring
fields.select()Dropdown (single choice)string
fields.radio()Radio group (single choice)string
fields.multiselect()Multiple choicestring[]
fields.relation()Reference records from a modelrecord or record[]
fields.repeater()Array of nested field groupsobject[]
fields.table()Editable table grid{ columns, rows }
fields.json()Raw JSON editorJSON value
fields.form()Pick a Cmssy formstring (form id)
fields.pageSelector()Pick page(s)PageRef[]

Most builders take only the base options below. fields.select(), fields.radio() and fields.multiselect() require an options array; fields.media() accepts multiple; fields.relation() needs a model (the referenced model's slug) and optionally mode: "all", multiple, sort and limit. A relation stores record id(s); the delivery API resolves them to full records before your component renders.


Defining a Block

A block is declared with defineBlock. Its editable fields live under props:

import { defineBlock, fields } from "@cmssy/react";
import Hero from "./Hero";

export const heroProps = {
  heading: fields.text({ label: "Heading", required: true, defaultValue: "Welcome" }),
  description: fields.textarea({ label: "Description" }),
};

export const heroBlock = defineBlock({
  type: "hero",
  label: "Hero",
  component: Hero,
  props: heroProps,
});

Export the props object on its own and type the component from it - BlockProps<typeof heroProps> - so the schema stays the only place a field is named, and the value types in the table above become the types your component sees. See Block development.


Field Options

Every fields.*() builder accepts the same base options:

fields.text({
  label: "Heading",
  required: true,
  defaultValue: "Welcome",
  placeholder: "Enter text",
  helperText: "Main heading",
})

Available keys: label, required, defaultValue, placeholder, helperText. Select and repeater fields add a few more (below).


Select & Multiselect

Options are a plain array of strings:

props: {
  layout: fields.select({
    label: "Layout",
    options: ["grid", "list", "carousel"],
    defaultValue: "grid",
  }),
  tags: fields.multiselect({
    label: "Tags",
    options: ["featured", "new"],
  }),
}

Repeater (Arrays)

Use itemSchema — a map of nested fields.*() — to describe each row:

props: {
  features: fields.repeater({
    label: "Features",
    maxItems: 6,
    itemSchema: {
      icon: fields.text({ label: "Icon Name", defaultValue: "Star" }),
      title: fields.text({ label: "Title", required: true }),
      description: fields.textarea({ label: "Description" }),
    },
  }),
}

In your component:

export default function Features({ content }) {
  return (
    <div className="grid grid-cols-3 gap-6">
      {(content.features ?? []).map((f, i) => (
        <div key={i}><h3>{f.title}</h3><p>{f.description}</p></div>
      ))}
    </div>
  );
}

Repeater also supports itemLabel, addButtonLabel, minItems, maxItems, and collapsible.