Forms
Forms are a built-in feature for collecting submissions from site visitors — contact forms, newsletter signups, surveys. Editors build forms visually in the admin, and developers render them with a single component.
Two collections ship with the CMS:
forms— form definitions (fields, labels, notification email, success message)form-submissions— captured entries, with anew | read | archivedstatus
Creating a form in the admin
Section titled “Creating a form in the admin”- Open
/admin/forms→ New form. - Give it a title (e.g., “Contact”). The slug auto-generates from the title and is used in the public component to reference this form.
- Optionally set:
- Redirect URL after submit — redirect visitors to a thank-you page.
- Success message — text shown on the form page after a successful submit (falls back to “Thanks — we got your message.” if empty).
- Notification email — an address to email on every submission (requires
RESEND_API_KEY).
- Add fields — each field is a block. Supported types:
| Type | Options |
|---|---|
text | name, label, placeholder, required, maxLength |
email | name, label, placeholder, required |
textarea | name, label, placeholder, required, rows |
select | name, label, required, options (array of strings) |
checkbox | name, label, required |
name is the form data key (e.g., email) — used in the stored submission’s data object.
label is what the visitor sees.
Rendering on the public site
Section titled “Rendering on the public site”---import CmsForm from "@/components/CmsForm.astro";---
<CmsForm slug="contact" />That’s it. The component fetches the form definition by slug, renders native HTML inputs (no client JS needed), submits to /api/cms/forms/submit/{slug}, and redirects back with ?submitted=1 on success so the success message appears.
Attaching page context
Section titled “Attaching page context”To know which page a submission came from, pass a context prop:
---import CmsForm from "@/components/CmsForm.astro";import { cms } from "@/cms/.generated/api";
const post = await cms.posts.findOne({ slug: Astro.params.slug! });---
<CmsForm slug="contact" context={{ pageTitle: post.title, pageUrl: Astro.url.pathname }} />Each key-value pair becomes a hidden input prefixed with _ctx_. The server merges them into the submission’s data._context object — visible alongside submitted fields on the submission detail page.
Viewing and moderating submissions
Section titled “Viewing and moderating submissions”Forms appear in the admin sidebar (between Menus and Authors) with a muted “N new” count. Open a form and click the Submissions tab to see its entries:
- Each row: submitted date, status, preview of the data.
- Clicking a row opens a read-only detail view with all submitted fields +
_contextmetadata rendered as a table. - Bulk status change — select rows and use Mark as new/read/archived to moderate in bulk.
- Status
newentries count toward the unread badge on the sidebar.
Spam protection
Section titled “Spam protection”The rendered form includes a honeypot field (_hp) hidden via CSS. Bots typically fill every visible input, so non-empty _hp submissions are silently accepted but never stored.
This is enough for most small sites. For heavier traffic, add a beforeCreate hook on form-submissions that calls Turnstile or reCAPTCHA.
How it’s wired
Section titled “How it’s wired”| Piece | Location |
|---|---|
| Form definition collection | src/cms/collections/forms.ts |
| Submission storage collection | src/cms/collections/form-submissions.ts |
| Public render component | src/components/CmsForm.astro |
| Submit endpoint | src/cms/routes/api/forms/submit/[slug].ts |
| Email notification | sendFormSubmissionEmail in src/cms/adapters/email.ts |
| Auth middleware public-route allowlist | src/cms/middleware/auth.ts |
Removing the forms feature
Section titled “Removing the forms feature”Because everything is first-class collections and a route, removing it is straightforward:
- Delete
src/cms/collections/forms.tsandform-submissions.ts. - Unregister both from
src/cms/cms.config.ts. - Delete
src/components/CmsForm.astroandsrc/cms/routes/api/forms/. - Remove the
isFormSubmitallowlist entry fromsrc/cms/middleware/auth.ts. - Remove
formsfrompinnedOrder/pinnedIconsinsrc/cms/admin/layouts/AdminLayout.astro.
No framework knob needed — the feature is just code.