7.1 KiB
| title | aliases | tags | sources | created | updated | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Form Builder Plugin |
|
|
|
2026-05-15 | 2026-05-15 |
Overview
@payloadcms/plugin-form-builder lets admins build and manage forms dynamically from the Admin Panel — no hard-coding new forms in the codebase. The front-end maps over the schema and renders its own UI components. All submissions are stored in the DB; confirmations can be on-screen messages or redirects; dynamic emails are sent on submission.
Replaces third-party services (HubSpot, Mailchimp) with first-party tooling.
Installation
pnpm add @payloadcms/plugin-form-builder
import { formBuilderPlugin } from '@payloadcms/plugin-form-builder'
export default buildConfig({
plugins: [formBuilderPlugin({ /* options */ })],
})
Key Options
| Option | Type | Purpose |
|---|---|---|
fields |
object | Enable/disable/override individual field types |
redirectRelationships |
string[] | Collection slugs available as redirect targets |
beforeEmail |
hook | Transform emails just before they are sent |
defaultToEmail |
string | Fallback recipient if form config omits to |
formOverrides |
CollectionConfig | Override the forms collection (slug, access, fields) |
formSubmissionOverrides |
CollectionConfig | Override the form-submissions collection |
handlePayment |
hook | Process payment on submission (use with payment field) |
uploadCollections |
string[] | Required when upload field is enabled |
Security Warning
The forms collection is publicly readable by default — including the emails field. Override formOverrides.access to restrict it, especially if you have frontend users.
formBuilderPlugin({
formOverrides: {
access: {
read: ({ req: { user } }) => !!user,
},
fields: ({ defaultFields }) => [...defaultFields, { name: 'custom', type: 'text' }],
},
})
Available Field Types
| Field | Maps to | Notes |
|---|---|---|
text |
<input type="text"> |
Simple string |
textarea |
<textarea> |
Multiline string |
select |
<select> |
Options array with label/value |
radio |
radio group | Options array with label/value |
email |
<input type="email"> |
Email with format validation |
state |
<select> |
US states |
country |
<select> |
Countries |
checkbox |
<input type="checkbox"> |
Boolean |
date |
<input type="date"> |
UTC stored; timezone configurable |
number |
<input type="number"> |
Numeric |
message |
RichText | Display-only content block in form |
payment |
— | Triggers handlePayment on submit; disabled by default |
upload |
file input | Disabled by default; requires uploadCollections config |
All fields share common properties: name, label, defaultValue, width, required.
Field Overrides
import { fields } from '@payloadcms/plugin-form-builder'
formBuilderPlugin({
fields: {
text: {
...fields.text,
labels: { singular: 'Custom Text Field', plural: 'Custom Text Fields' },
},
date: false, // disable
},
})
GraphQL Name Collision Fix
If your own blocks/collections use the same names as plugin fields (e.g. Country):
formBuilderPlugin({
fields: {
country: { interfaceName: 'CountryFormBlock' },
},
})
Upload Fields
Disabled by default. Enable and configure upload collections:
formBuilderPlugin({
fields: {
upload: {
uploadCollections: ['media', 'documents'], // required
},
},
})
Frontend submission via multipart/form-data:
const formData = new FormData()
formData.append(field.name, fileValue) // upload fields
formData.append('_payload', JSON.stringify({ form: formId, submissionData }))
await fetch('/api/form-submissions', { method: 'POST', body: formData })
The server validates MIME types/sizes, uploads to the collection, and stores file IDs.
Alternative: pre-upload then submit ID — upload file first via POST /api/{collection}, then pass the returned doc.id as the field value.
Presigned URL flow (for large files with S3/GCS/Azure clientUploads enabled):
POST /api/storage-s3-generate-signed-url→ get presigned URLPUTfile directly to cloud storagePOST /api/{collection}with metadata to create the DB document
Payment Integration
import { getPaymentTotal } from '@payloadcms/plugin-form-builder'
formBuilderPlugin({
handlePayment: async ({ form, submissionData }) => {
const paymentField = form.fields?.find(f => f.blockType === 'payment')
const price = getPaymentTotal({
basePrice: paymentField.basePrice,
priceConditions: paymentField.priceConditions,
fieldValues: submissionData,
})
// call your payment processor here
},
})
priceConditions define conditional pricing rules (field → condition → operator → value → delta).
Uses Payload's wiki/payloadcms/email (Nodemailer/Resend). Supports:
- Dynamic field interpolation:
Thank you, {{name}}! {{*}}wildcard — outputs all fields askey: value{{*:table}}— outputs all fields as HTML table- Rich text body serialized to HTML server-side
beforeEmail hook — inject HTML templates before sending:
beforeEmail: (emailsToSend) =>
emailsToSend.map(email => ({ ...email, html: wrapTemplate(email.html) }))
SendGrid gotcha: if using Link Branding + Domain Authentication, the from address must be on your verified domain. from: {{email}} will fail — use website@your_domain.com instead.
TypeScript
import type {
PluginConfig, Form, FormSubmission,
FieldsConfig, BeforeEmail, HandlePayment,
UploadField, UploadFieldMimeType,
} from '@payloadcms/plugin-form-builder/types'
Key Takeaways
- Install
@payloadcms/plugin-form-builder, add toplugins[]— createsformsandform-submissionscollections automatically formscollection is world-readable by default — overrideformOverrides.accessin production- Upload field is disabled by default — enable it and set
uploadCollections - Payment field calls
handlePaymenthook; usegetPaymentTotal()for conditional pricing - Email uses Payload's email config with
{{fieldName}}interpolation; SendGrid domain auth restrictsfromaddress - GraphQL type name collisions from plugin fields → fix with
interfaceNameoverride form-submissionsallows publiccreatebut blocksupdateandreadby default (correct for public forms)
Related
- wiki/payloadcms/plugins — all 10 plugins overview
- wiki/payloadcms/email — Nodemailer, Resend, attachments
- wiki/payloadcms/upload — upload collections, storage adapters
- wiki/payloadcms/hooks-collections — beforeChange, afterChange patterns
- wiki/payloadcms/authentication-overview — access control on collections
Sources
raw/plugins__form-builder.md— official Payload CMS Form Builder Plugin docs