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

  1. Add a pageSelector field to your block schema (e.g., parentPage)
  2. The platform detects this field and fetches child pages server-side (SSR)
  3. Page data is injected into context.pages automatically
  4. 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:

  1. 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.
  2. 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.
  3. Design Library - Browse and install pre-built blocks from the Cmssy library. One-click install adds the block to your workspace.

Block Lifecycle

  1. Definition - Block type is registered in workspace with schema and compiled component
  2. Instantiation - User drags block onto page; instance created with default content and unique UUID
  3. Editing - User edits content via admin panel; schema defines the editing UI
  4. Publishing - Page is published; block content is frozen as publishedBlocks
  5. Rendering - Frontend loads block component from blob URL; renders with content and PlatformContext

Schema Field Types

TypeDescriptionValue TypeExample
singleLineShort text inputstringHeading, button text
multiLineTextarea for longer contentstringDescription, paragraph
richTextWYSIWYG editor with formattingstringArticle body, bio
numericNumeric inputnumberPrice, quantity
dateDate pickerstringPublished date
mediaImage/video uploadstringHero image, avatar
linkURL picker (internal/external)stringButton URL, social link
selectDropdown selectstringLayout variant, theme
multiselectMultiple choice selectionstring[]Tags, categories
booleanTrue/false switchbooleanShow/hide section
colorColor pickerstringAccent color
repeaterArray of objects with sub-fieldsRecord<string, unknown>[]Feature list, FAQ items
formForm picker - embed a workspace formstringContact form, newsletter
emailTemplateEmail template pickerstringConfirmation email
emailConfigurationEmail configuration pickerstringSMTP settings
pageSelectorSelect pages from workspaceArray<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

Block System - Cmssy Documentation | Cmssy