205 lines
7.1 KiB
Markdown
205 lines
7.1 KiB
Markdown
---
|
|
title: "Form Builder Plugin"
|
|
aliases: [form-builder, plugin-form-builder, payloadcms-forms]
|
|
tags: [payloadcms, plugin, forms, email, payments, uploads]
|
|
sources: [raw/plugins__form-builder.md]
|
|
created: 2026-05-15
|
|
updated: 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
|
|
|
|
```bash
|
|
pnpm add @payloadcms/plugin-form-builder
|
|
```
|
|
|
|
```ts
|
|
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.
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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`):
|
|
|
|
```ts
|
|
formBuilderPlugin({
|
|
fields: {
|
|
country: { interfaceName: 'CountryFormBlock' },
|
|
},
|
|
})
|
|
```
|
|
|
|
## Upload Fields
|
|
|
|
Disabled by default. Enable and configure upload collections:
|
|
|
|
```ts
|
|
formBuilderPlugin({
|
|
fields: {
|
|
upload: {
|
|
uploadCollections: ['media', 'documents'], // required
|
|
},
|
|
},
|
|
})
|
|
```
|
|
|
|
**Frontend submission via `multipart/form-data`:**
|
|
|
|
```ts
|
|
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):
|
|
1. `POST /api/storage-s3-generate-signed-url` → get presigned URL
|
|
2. `PUT` file directly to cloud storage
|
|
3. `POST /api/{collection}` with metadata to create the DB document
|
|
|
|
## Payment Integration
|
|
|
|
```ts
|
|
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).
|
|
|
|
## Email
|
|
|
|
Uses Payload's [[wiki/payloadcms/email|email configuration]] (Nodemailer/Resend). Supports:
|
|
- Dynamic field interpolation: `Thank you, {{name}}!`
|
|
- `{{*}}` wildcard — outputs all fields as `key: value`
|
|
- `{{*:table}}` — outputs all fields as HTML table
|
|
- Rich text body serialized to HTML server-side
|
|
|
|
**`beforeEmail` hook** — inject HTML templates before sending:
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
import type {
|
|
PluginConfig, Form, FormSubmission,
|
|
FieldsConfig, BeforeEmail, HandlePayment,
|
|
UploadField, UploadFieldMimeType,
|
|
} from '@payloadcms/plugin-form-builder/types'
|
|
```
|
|
|
|
## Key Takeaways
|
|
|
|
- Install `@payloadcms/plugin-form-builder`, add to `plugins[]` — creates `forms` and `form-submissions` collections automatically
|
|
- `forms` collection is **world-readable by default** — override `formOverrides.access` in production
|
|
- Upload field is **disabled by default** — enable it and set `uploadCollections`
|
|
- Payment field calls `handlePayment` hook; use `getPaymentTotal()` for conditional pricing
|
|
- Email uses Payload's email config with `{{fieldName}}` interpolation; SendGrid domain auth restricts `from` address
|
|
- GraphQL type name collisions from plugin fields → fix with `interfaceName` override
|
|
- `form-submissions` allows public `create` but blocks `update` and `read` by default (correct for public forms)
|
|
|
|
## Related
|
|
|
|
- [[wiki/payloadcms/plugins|Official Plugins]] — all 10 plugins overview
|
|
- [[wiki/payloadcms/email|Email — Adapters]] — Nodemailer, Resend, attachments
|
|
- [[wiki/payloadcms/upload|Upload & Media]] — upload collections, storage adapters
|
|
- [[wiki/payloadcms/hooks-collections|Collection Hooks]] — beforeChange, afterChange patterns
|
|
- [[wiki/payloadcms/authentication-overview|Authentication Overview]] — access control on collections
|
|
|
|
## Sources
|
|
|
|
- `raw/plugins__form-builder.md` — official Payload CMS Form Builder Plugin docs
|