Block Schema & Field Types
All field types, the defineBlock + fields builder, field options, repeaters, and select options.
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.
| Builder | Description | Value Type |
|---|---|---|
fields.text() | Single-line text input | string |
fields.textarea() | Multi-line text area | string |
fields.richText() | WYSIWYG editor (Tiptap) | string (HTML) |
fields.markdown() | Markdown editor | string (Markdown) |
fields.number() | Number input | number |
fields.date() | Date picker | string (ISO date) |
fields.datetime() | Date & time picker | string (ISO) |
fields.boolean() | Toggle switch | boolean |
fields.color() | Color picker | string (hex) |
fields.media() | Image/video upload | ResolvedMedia or ResolvedMedia[] |
fields.link() | Internal/external link | string |
fields.url() | URL input | string |
fields.email() | Email input | string |
fields.select() | Dropdown (single choice) | string |
fields.radio() | Radio group (single choice) | string |
fields.multiselect() | Multiple choice | string[] |
fields.relation() | Reference records from a model | record or record[] |
fields.repeater() | Array of nested field groups | object[] |
fields.table() | Editable table grid | { columns, rows } |
fields.json() | Raw JSON editor | JSON value |
fields.form() | Pick a Cmssy form | string (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, localized. Select and repeater fields add a few more (below).
Fields That Are the Same in Every Language
By default every field is translated: each language holds its own value. A field that should hold one value for the whole site - a logo, a product photo, a brand colour - declares localized: false:
props: {
logo: fields.media({ label: "Logo", localized: false }),
heading: fields.text({ label: "Heading" }),
}
The editor shows such a field once, with a padlock, whichever language you are editing; changing it changes it for every language. The delivery API still returns it inside every language's content, so your component reads content.logo exactly as before - nothing changes on the consumer side.
localized is independent of tab: a field on the style or advanced tab is already one value per block, so the flag adds nothing there. Flipping the flag on an existing field moves the stored value for you on the next manifest push. Requires @cmssy/core 16.4.0 or newer.
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.