Forms

Define forms with field schemas, then collect submissions through a validated public endpoint. No controller code required.

Field definitions

The fields column stores a JSON array. Each definition supports:

PropertyTypeDescription
namestringRequired. Field name (key in submitted data)
typestringstring (default), number, boolean, email, uuid
requiredbooleanMust be present
min / maxnumberNumeric bounds (number type)
minLength / maxLengthnumberString length bounds
patternstringRegex the value must match (string/email)
enumarrayAllowed values (all types)
[
  { "name": "email", "type": "email", "required": true },
  { "name": "subject", "type": "string", "required": true, "minLength": 3 },
  { "name": "rating", "type": "number", "required": false, "min": 1, "max": 5 },
  { "name": "category", "type": "string", "enum": ["general", "support", "billing"] }
]

Submitting

POST to /forms/:id. JSON and form-encoded bodies are both accepted:

curl -X POST https://your-cms.dev/forms/<form-id> \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "message": "Hello!"}'
StatusDescription
201Success — {"success":true,"id":"<entry-id>"}
400Missing/invalid body or unsupported Content-Type
404Form not found
422Validation failed — returns details array

Submissions are stored in the form_entries table with the validated data as JSON.

Managing entries (admin)

GET    /api/forms/:id/entries           # list entries for a form
DELETE /api/forms/:id/entries/:entryId  # delete an entry

Frontend helpers

Submission fires a form_entry.created webhook, so you can trigger emails, notifications, or CRM syncs without polling. For spam protection, configure Turnstile, reCAPTCHA, or hCaptcha per form — the frontend fetches the provider config from /captcha-config and the submission includes the captcha token for server-side verification.

Rendering forms

The seeded form component renders a styled form from props and submits via AJAX:

{{ component name:"form" props:data.contactForm }}

Pass form_id, fields (name, label, type, placeholder, required, minLength, maxLength, rows), title, description, and submit_text — typically through a static data source on the page.