Block System
How blocks work in the headless model - defineBlock, instances, the component, context, and the data loader.
Overview
Blocks are the building units of every Cmssy page. In the headless model a block is a React component in your own Next.js repo, declared with defineBlock and a fields schema. The Cmssy admin lets editors place and configure block instances; your site renders them with the SDK. A block has three parts:
- Schema (
fields) — the editable fields shown in the admin - Component — the React component that renders the content
- Registration — the block added to your
cmssy/blocks.tsarray
Defining a block
// blocks/hero/block.ts
import { defineBlock, fields } from "@cmssy/react";
import Hero from "./Hero";
export const heroBlock = defineBlock({
type: "hero", // unique block type id, stored on each instance
label: "Hero",
component: Hero,
props: {
heading: fields.text({ label: "Heading" }),
body: fields.richText({ label: "Body" }),
},
});See Schema & Field Types for every field type, and Block Development for the full walkthrough.
Block instances
When an editor adds your block to a page, Cmssy stores a block instance:
{
id: string; // unique UUID for this instance
type: string; // matches your block's `type` (e.g. "hero")
content: Record<string, unknown>; // language-keyed field values
}Content is stored per language ({ en: {...}, pl: {...} }); the SDK resolves the active locale before passing content to your component, so you read fields directly.
The component, context & data
Your component receives { content, context, data }:
import type { CmssyBlockContext } from "@cmssy/react";
export default function Hero({ content, context }: {
content: Record<string, unknown>;
context?: CmssyBlockContext;
}) {
const locale = context?.locale.current ?? "en";
return <section><h1>{String(content.heading ?? "")}</h1></section>;
}context carries locale ({ current, default, enabled }) and isPreview (true inside the editor), plus forms, and — when config.auth is set — auth ({ isAuthenticated, member }) and workspace ({ id, slug }). For your own custom models or records, fetch with createCmssyClient from @cmssy/react. To fetch during SSR (crawlable, no loading flash), give the block a loader — its result arrives as the data prop. See Advanced Features for both.
Registering blocks
// cmssy/blocks.ts
import { heroBlock } from "@/blocks/hero/block";
export const blocks = [heroBlock];That array is the single source of truth: it drives rendering, and the editor learns each block's schema over the SDK bridge, so your block appears in the admin's block picker. There is no separate block build or publish step — blocks ship when you deploy your Next.js app.
Layout blocks
Header, footer and other shared regions are layout blocks — the same as page blocks but tagged with layoutPositions (e.g. ["header"]) and rendered by CmssyServerLayout per position.
Internationalization
Content is language-keyed in the CMS and resolved per request. Routing is by path prefix (/pl/*), with the default locale using clean URLs. Read the active and enabled locales from context.locale.
Next Steps
- Block Development — full walkthrough: define, component, register, ship
- Schema & Field Types — every field type, repeaters, groups
- Advanced Features — layout blocks, styling, server loaders