Block System
Understanding Cmssy's reusable block architecture
Overview
Blocks are the core building units of every Cmssy page. Each block is a self-contained UI component that can be placed, configured, and reused across pages. The block system is designed around four key concepts:
- Schema — Field definitions that describe editable content (text, images, links, repeaters)
- Default Content — Pre-filled values for new block instances
- Component — React component that renders the block on the page
- Styles — Optional CSS (Tailwind classes work out of the box)
Block Definition Interface
Every block is described by a BlockDefinition interface:
interface BlockDefinition {
type: string; // Unique identifier (e.g., 'hero')
name: string; // Display name in the editor
description: string; // Brief description
category: string; // Category for grouping
icon: string; // Icon for the sidebar
schema: SchemaField[]; // Editable field definitions
defaultContent: object; // Default values for new instances
componentUrl: string; // URL to compiled JS bundle
cssUrl?: string; // Optional CSS bundle URL
}Block Instance Structure
When a block is placed on a page, it becomes a block instance with unique content:
{
id: string; // Unique UUID for this instance
type: string; // Block type (e.g., 'hero')
content: { // Language-keyed content
en: {
heading: "Build faster",
subheading: "Ship with confidence"
},
pl: {
heading: "Buduj szybciej",
subheading: "Dostarczaj z pewnością"
}
}
}PlatformContext
Every block component receives a PlatformContext prop that provides runtime information:
interface PlatformContext {
language: string; // Current language ('en', 'pl')
isEditor: boolean; // True when rendering in admin editor
isPreview: boolean; // True in editor preview mode
workspace: { // Current workspace info
id: string;
slug: string;
};
siteConfig: SiteConfig; // Site-wide settings
navigation: NavItem[]; // Site navigation structure
apiBaseUrl: string; // Backend API URL
localizeHref: (href: string) => string; // Adds language prefix to URLs
pages?: { // Collection data (for collection blocks)
items: PageItem[]; // Array of pages
total: number;
hasMore: boolean;
};
}Use context.language to render the correct language version. Use context.isEditor to disable interactive elements (forms, modals) during editing. Use context.localizeHref to generate language-prefixed links.
Collection Blocks
Collection blocks can dynamically list CMS pages - perfect for blog post listings, product grids, or any content that comes from your page types.
How it works
- Add a
pageSelectorfield to your block schema (e.g.,parentPage) - The platform detects this field and fetches child pages server-side (SSR)
- Page data is injected into
context.pagesautomatically - Your block renders the list from
context.pages.items
PageItem interface
interface PageItem {
id: string;
slug: string;
fullSlug: string;
publishedAt: string | null;
displayName: Record<string, string>;
seoTitle: Record<string, string> | null;
seoDescription: Record<string, string> | null;
customFields: Array<{ fieldKey: string; value: unknown }>;
pageType: string;
}Accessing custom fields
function getCustomField(item: PageItem, key: string): unknown {
const field = item.customFields?.find((f) => f.fieldKey === key);
return field?.value ?? null;
}
// Usage
const coverImage = getCustomField(item, "cover_image") as string | null;
const author = getCustomField(item, "author") as string | null;Client-side features
Collection blocks can add search, filtering, and infinite scroll on the client side. Use context.workspace.id and the /api/graphql endpoint to fetch more pages:
const res = await fetch('/api/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `query PublicPagesByType($workspaceId: String!, $parentSlug: String, $search: String, $limit: Int, $offset: Int) {
publicPagesByType(workspaceId: $workspaceId, parentSlug: $parentSlug, search: $search, limit: $limit, offset: $offset) {
items { id slug fullSlug publishedAt displayName seoDescription customFields pageType }
total hasMore
}
}`,
variables: { workspaceId: context.workspace.id, parentSlug, search, limit, offset }
})
});Built-in Block Library
Cmssy comes with a growing collection of pre-built blocks across categories like Marketing (hero, pricing, FAQ, contact forms), Documentation (articles, code blocks, step guides), and Layout (header, footer). Browse and install them from the Design Library in the admin panel - no coding required.
Custom Blocks
You can create custom blocks in three ways:
- AI Block Builder - Describe what you want in natural language and AI generates the full block (component + schema + styles). Available in the admin panel under Blocks > Create with AI.
- CLI Import - Build blocks locally with React/Tailwind and import them using
cmssy import. The CLI bundles your code, uploads it to blob storage, and registers the block in your workspace. - Design Library - Browse and install pre-built blocks from the Cmssy library. One-click install adds the block to your workspace.
Block Lifecycle
- Definition - Block type is registered in workspace with schema and compiled component
- Instantiation - User drags block onto page; instance created with default content and unique UUID
- Editing - User edits content via admin panel; schema defines the editing UI
- Publishing - Page is published; block content is frozen as
publishedBlocks - Rendering - Frontend loads block component from blob URL; renders with content and PlatformContext
Schema Field Types
| Type | Description | Value Type | Example |
|---|---|---|---|
singleLine | Short text input | string | Heading, button text |
multiLine | Textarea for longer content | string | Description, paragraph |
richText | WYSIWYG editor with formatting | string | Article body, bio |
numeric | Numeric input | number | Price, quantity |
date | Date picker | string | Published date |
media | Image/video upload | string | Hero image, avatar |
link | URL picker (internal/external) | string | Button URL, social link |
select | Dropdown select | string | Layout variant, theme |
multiselect | Multiple choice selection | string[] | Tags, categories |
boolean | True/false switch | boolean | Show/hide section |
color | Color picker | string | Accent color |
repeater | Array of objects with sub-fields | Record<string, unknown>[] | Feature list, FAQ items |
form | Form picker - embed a workspace form | string | Contact form, newsletter |
emailTemplate | Email template picker | string | Confirmation email |
emailConfiguration | Email configuration picker | string | SMTP settings |
pageSelector | Select pages from workspace | Array<PageRef> | Parent page for collections |
Page Selector & PageRef
The pageSelector field type lets blocks reference other pages in the workspace. Selected pages are stored as PageRef objects containing the slug and localized display names:
interface PageRef {
slug: string; // Page slug (e.g., '/about')
displayName: Record<string, string>; // Localized names { en: 'About', pl: 'O nas' }
}When used with collection blocks, the pageSelector field determines which page's children to list. The platform automatically fetches and injects the data into context.pages.
Conditional Fields
Use showWhen to conditionally show fields based on other field values:
showButton: {
type: "boolean",
label: "Show CTA Button",
defaultValue: true,
},
buttonText: {
type: "singleLine",
label: "Button Text",
showWhen: { field: "showButton", equals: true },
}Internationalization (i18n)
Block content is stored per language. Each language key contains the full set of fields:
{
"content": {
"en": { "heading": "Hello World" },
"pl": { "heading": "Witaj Świecie" }
}
}The editor provides a language switcher to edit content in each enabled language. The default language content is always required; additional languages are optional.
Next Steps
- Templates - Compose blocks into full page layouts
- Theming - Customize colors, typography, and spacing
- Block Development Guide - Build custom blocks with the CLI