Now with AI-powered page building via MCP Server
Documentation

Block System

How blocks work in the headless model - defineBlock, instances, the component, and context.

Last updated: June 7, 2026

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.ts array

Defining a block

// blocks/hero/block.ts
import type { ComponentType } from "react";
import { defineBlock, fields } from "@cmssy/react";
import Component from "./src";

export const heroBlock = defineBlock({
  type: "hero",          // unique block type id, stored on each instance
  label: "Hero",
  component: Component as unknown as ComponentType<{ content: Record<string, unknown> }>,
  props: {
    heading: fields.singleLine({ 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

Your component receives { content, context }:

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 is { locale: { current, default, enabled }, isPreview }. Locale-aware UI reads context.locale.enabled; isPreview is true inside the editor. For workspace data, custom models, records or forms, use createCmssyClient from @cmssy/react rather than context.


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