Now with AI-powered page building via MCP Server

Forms & Form Builder

Create forms with the visual Form Builder and connect them to custom blocks by storing the form's ID in a block field.

Last updated: March 31, 2026

Overview

Cmssy has a built-in Form Builder that lets you create forms visually and connect them to any custom block. Forms handle validation, submissions, email notifications, and webhooks — so your blocks only need to render the UI.

The system has two parts:

  1. Form Builder (Dashboard > Forms) — define fields, validation, and actions
  2. A formId field on your block — stores which form the block should render

Creating a Form

1. Open the Form Builder

Go to Dashboard → Forms → Create Form. Give it a name and slug (e.g. contact-form).

2. Add Fields

Each field has:

PropertyDescription
nameUnique field key (e.g. email, message)
typetext, email, textarea, number, phone, url, date, select, multiselect, checkbox, radio, file, hidden
labelDisplay label (multilingual)
placeholderPlaceholder text (multilingual)
validationrequired, minLength, maxLength, minValue, maxValue, pattern, customMessage
widthfull, half, or third — controls field width in the form grid
optionsFor select, multiselect, radio — array of { value, label }
orderDisplay order

3. Configure Settings

SettingDescription
Action Typecontact (email + save), newsletter (subscribe), login, register, custom (webhook)
Email RecipientsList of emails that receive form notifications
Webhook URLFor custom action — POST submission data to an external endpoint
Success MessageShown after successful submission (multilingual)
Error MessageShown on failure (multilingual)
Submit Button LabelButton text (multilingual)
Save SubmissionsStore submissions in the database (default: true)
Send Email NotificationEmail recipients on each submission (default: true)
Enable CaptchaSpam protection
Require LoginOnly logged-in users can submit

4. Publish

Set status to Published. Copy its form ID — you'll reference it from your block.

Using Forms in Custom Blocks

Referencing a Form

Add a formId field to your block schema and paste the published form's ID into it from the editor:

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

export const contactBlock = defineBlock({
  type: "contact",
  label: "Contact",
  component: Component as unknown as ComponentType<{ content: Record<string, unknown> }>,
  props: {
    formId: fields.singleLine({ label: "Form", helperText: "Paste the form ID from the Form Builder" }),
    submitLoadingText: fields.singleLine({ label: "Loading Text", defaultValue: "Sending..." }),
    successHeading: fields.singleLine({ label: "Success Heading", defaultValue: "Message Sent!" }),
  },
});

Rendering the Form

The SDK automatically resolves any form referenced by a block's formId and injects the definition into the block context as context.forms[formId] — no fetching needed:

// blocks/contact/src/Contact.tsx
export default function Contact({ content, context }) {
  const { formId } = content;
  const formDef = formId ? context?.forms?.[formId] ?? null : null;
  // formDef.fields   - array of field definitions
  // formDef.settings - submit label, messages, action type
  // ...render the fields
}

Submitting the Form

Submit from a server action using client.queryScoped with the exported SUBMIT_FORM_MUTATION. It validates fields, saves the submission, sends email notifications, calls any webhook, and returns a success/error response:

// blocks/contact/src/actions.ts
"use server";
import { createCmssyClient, SUBMIT_FORM_MUTATION, type CmssyFormSubmitResponse } from "@cmssy/react";

const client = createCmssyClient({
  apiUrl: process.env.CMSSY_API_URL!,
  workspaceSlug: process.env.CMSSY_WORKSPACE_SLUG!,
});

export async function submitForm(formId: string, data: Record<string, string>) {
  const res = await client.queryScoped<{ submitForm: CmssyFormSubmitResponse }>(
    SUBMIT_FORM_MUTATION,
    { formId, input: { data } },
    { workspaceId: process.env.CMSSY_WORKSPACE_ID },
  );
  return res.submitForm; // { success, message }
}

Reference Form Blocks

Cmssy doesn't ship blocks — in the headless model you build blocks in your own repo with the SDK. These are common form blocks you can implement as reference patterns, each wiring a Form Builder form (its actionType) to a block you render:

BlockForm ActionDescription
ContactcontactContact form with info cards and quote
NewsletternewsletterEmail signup form
LoginloginAuthentication form
RegisterregisterUser registration
Forgot PasswordloginPassword reset request

Each defines the form server-side in the Form Builder and renders it in a block via a formId field — the dev owns the markup. See Member Authentication for the auth flows.

Form Submissions

Viewing Submissions

Go to Dashboard → Forms → [Your Form] → Submissions. Each submission shows:

  • All field values
  • Status (pending, processed, spam, archived)
  • IP address, user agent, referrer
  • Timestamp
  • Email/webhook delivery status

Submission Lifecycle

  1. User submits form on your published site
  2. Server validates fields and checks rate limits
  3. Submission is saved with status pending
  4. Email notification sent to recipients (if enabled)
  5. Webhook called (if configured)
  6. Status updated to processed

Spam Protection

Forms include built-in rate limiting:

  • Per-IP rate limit per form
  • Optional captcha support
  • Honeypot fields can be added as hidden field type

Action Types Reference

contact

Saves submission + sends email to configured recipients. The default for most forms.

newsletter

Subscribes the email field to the workspace newsletter list.

login / register

Handles authentication. These are special action types used by the auth form blocks you build (see Member Authentication).

custom

POSTs the submission data as JSON to your webhook URL. Useful for integrating with external services (Zapier, Slack, CRMs, etc.).

Tips

  • Always publish your form before referencing it — draft forms can't be resolved
  • Use half and third widths to create multi-column form layouts (e.g. first name + last name side by side)
  • Multilingual labels — form fields support per-language labels and placeholders, matching the site's language system
  • Test submissions are saved like real ones — delete them from the Submissions tab when done
  • Webhook debugging — check the submission's webhookResponse field for response details from your endpoint