packstub / filament-form-builder
Form builder for Filament panels: build forms in the admin, render them with Blade, Livewire or JSON, collect and export submissions.
Fund package maintenance!
Requires
- php: ^8.3
- filament/filament: ^4.0 || ^5.0
- illuminate/contracts: ^12.0 || ^13.0
- spatie/laravel-package-tools: ^1.92
Requires (Dev)
- laravel/pint: ^1.27
- orchestra/testbench: ^10.0 || ^11.0
- pestphp/pest: ^4.0 || ^5.0
- pestphp/pest-plugin-laravel: ^4.0 || ^5.0
- pestphp/pest-plugin-livewire: ^4.0 || ^5.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Build forms in your Filament panel, put them on your site with one Blade tag, a Livewire component or a JSON call, and read the submissions where you already work.
Features
- Builder in the panel — a Forms resource with a block per field type: text, email, phone, URL, number, long text, dropdown, radio buttons, checkbox, checkbox list, date, hidden, heading and paragraph. Every field has a label, key, placeholder, help text, default, required flag, width and any extra Laravel validation rule you type in.
- Three ways to render — a plain Blade component that works on cached and session-less pages, a Livewire component with Filament fields and in-place validation, and a JSON API for SPAs and mobile apps. All three go through the same validation and storage.
- Submissions — stored with the values, the page they came from and the labels at the time, listed per form with a details slide-over, read / unread state, filters, bulk actions and a CSV export. Unread counts on the navigation item.
- Notifications and sinks — an email per submission to any list of addresses, a
SubmissionReceivedevent, and aSubmissionSinkcontract for the CRM, webhook or mailing list you want to feed. - Spam protection without a captcha — a honeypot, a time trap (an encrypted token issued with the form) and a per-IP rate limit. Bots see the success message; nothing is stored.
- Availability — a success message or a redirect, an active switch, an opening and closing date, an optional login requirement, and a "do not store" mode for forms that should only be emailed.
- Extensible — write a field type class with its own settings, rules and views; swap the models and table names; submit from code with
FormBuilder::submit(). - Themeable and translatable — the Blade renderer uses CSS variables with sensible defaults and ships its own small stylesheet; every string lives in a language file.
Compatibility
| Plugin | Filament | Laravel | PHP |
|---|---|---|---|
| 1.x | 4.x, 5.x | 12.x, 13.x | 8.3+ |
Installation
composer require packstub/filament-form-builder php artisan packstub-form-builder:install
Register the plugin on your panel:
use Packstub\FormBuilder\FormBuilderPlugin; $panel->plugin(FormBuilderPlugin::make());
A Forms resource appears in the navigation.
Building a form
Create a form, give it a name, and add fields from the block picker. Each block carries the settings of its type: choices for a dropdown or a checkbox list, a range for numbers and dates, rows for long text, the level of a heading. Keys are derived from labels and kept unique, so values in submissions and exports keep a stable name.
The Settings tab holds the submit button label, the success message or a redirect URL, notification addresses, whether submissions are stored, an opening and closing date, a login requirement and the spam settings. The Embed tab shows the snippets for the form you are editing.
Rendering a form
Blade
<x-form-builder::form form="contact" />
A plain HTML form posting to /forms/contact. Without JavaScript the browser posts and comes back to the page with the success message or the errors and the old input. With the small script the component inlines (frontend.enhance), the form submits with fetch and shows the result in place, without reloading.
The component works on pages without a session: errors and old input travel in an encrypted query parameter instead of the session. The submit route runs the web middleware by default; on a session-less site set routes.middleware to [] and rely on the honeypot, the time trap and the rate limit.
Options: :enhance="false" for a plain POST only, :styles="false" when your site ships its own CSS, action and return to override the endpoint and the page to come back to.
Livewire
<livewire:form-builder form="contact" />
The fields as Filament components, validated in place. The page needs Filament's frontend assets:
@filamentStyles @filamentScripts
JSON
GET /forms/contact/definition POST /forms/contact Accept: application/json
The definition lists the fields with their type, rules and choices, plus a fresh protection token and the names of the anti-spam fields to send back. The POST answers { "ok": true, "message": "...", "redirect": null, "id": 12 } or 422 with errors keyed by field.
Hosted page
Every form is also served on its own at /forms/{slug} (switch off with routes.page, change the layout with routes.page_layout).
Submissions
Submissions live under the form as a relation manager: the date, a summary of the first values, the page they came from and the channel (web, json, livewire, code). Opening one shows every value and the details, and marks it read. Bulk actions mark as read, export or delete. The CSV export lists the form's current fields, then any key an older submission still carries.
use Packstub\FormBuilder\Models\FormSubmission; FormSubmission::query()->unread()->count(); $submission->value('email'); $submission->formatted(); // ['email' => ['label' => 'Email', 'value' => 'ada@example.com'], ...]
Notifications and sinks
Add addresses to Notify by email and each submission is emailed (queued when a queue is configured). For anything else, write a sink:
use Packstub\FormBuilder\Contracts\SubmissionSink; use Packstub\FormBuilder\Models\FormSubmission; class PushToCrm implements SubmissionSink { public function handle(FormSubmission $submission): void { Crm::createLead($submission->form->slug, $submission->data); } }
Register it in config/packstub-form-builder.php under sinks, or at runtime with FormBuilder::sink(PushToCrm::class). The SubmissionReceived and SpamDetected events are dispatched as well.
Spam protection
- Honeypot — a hidden input a person never sees; a filled one drops the submission.
- Time trap — the form carries an encrypted token with the time it was rendered; a submission posted faster than
spam.min_seconds(default 2) is dropped. Headless clients get the token from the definition endpoint. - Rate limit —
submissions.throttle(default10,1) per IP on the submit endpoint.
Both checks can be tuned per form in its Settings tab. Dropped submissions look like a success to the sender and fire SpamDetected.
Settings
| Setting | What it does |
|---|---|
| Active | Off: the form renders as closed and rejects submissions |
| Submit button, success message | Shown by every renderer |
| Redirect after submit | Sends the visitor to a URL instead of showing the message |
| Notify by email | One email per submission to each address |
| Store submissions | Off: the submission is only emailed and passed to sinks |
| Opens at, closes at | An availability window |
| Require a logged-in user | Rejects anonymous submissions |
| Honeypot, minimum seconds | Per-form spam settings |
Extending
A field type is a class: its id, an icon, the settings it shows in the builder, its validation rules, how it normalises a value, a Blade view and a Filament component.
use Filament\Forms\Components\TextInput; use Packstub\FormBuilder\Fields\Field; use Packstub\FormBuilder\Fields\Types\NumberField; class RatingField extends NumberField { public static function id(): string { return 'rating'; } public function rules(Field $field): array { return ['integer', 'between:1,5']; } }
Register it in the config under field_types, on the plugin with FormBuilderPlugin::make()->fieldTypes([RatingField::class]), or with FormBuilder::registerFieldTypes([...]). Hide built-ins with ->withoutFieldTypes([...]). Name the label in your language file under packstub-form-builder::form-builder.types.rating.
Submit from code:
use Packstub\FormBuilder\Facades\FormBuilder; FormBuilder::submit('contact', ['name' => 'Ada', 'email' => 'ada@example.com', 'message' => 'Hi']);
Theming
The Blade renderer scopes everything under .fb-form and reads these variables, each with a fallback:
:root { --fb-color-text: #0f172a; --fb-color-muted: #475569; --fb-color-border: #cbd5e1; --fb-color-surface: #fff; --fb-color-primary: #2563eb; --fb-color-primary-contrast: #fff; --fb-color-danger: #dc2626; --fb-color-success: #15803d; --fb-radius: .5rem; --fb-font: inherit; }
Publish the stylesheet and script with php artisan vendor:publish --tag=packstub-form-builder-assets and set frontend.styles / frontend.enhance to false to ship them your own way. Views publish with --tag=packstub-form-builder-views, the language file with --tag=packstub-form-builder-translations.
Configuration
php artisan vendor:publish --tag=packstub-form-builder-config
| Key | Default | What it does |
|---|---|---|
tables.*, models.* |
form_builder_* |
Table names and model classes |
field_types |
the built-ins | Field types offered in the builder |
routes.prefix |
forms |
URL prefix of the submit, definition and hosted page routes |
routes.middleware |
['web'] |
Middleware of the submit and definition routes |
routes.page, page_middleware, page_layout |
on, ['web'], package layout |
The hosted page |
submissions.throttle |
10,1 |
Rate limit per IP; null to disable |
submissions.store_ip, store_user_agent |
true |
What the submission row records |
submissions.queue_notifications |
true |
Queue the notification emails |
spam.honeypot, honeypot_field, min_seconds, token_field |
on, _fb_website, 2, _fb_token |
Spam defaults (per form in the panel) |
sinks |
[] |
SubmissionSink classes |
frontend.styles, frontend.enhance |
true |
Inline the stylesheet and the fetch script |
navigation.*, gate |
— | Navigation group, icon, sort, unread badge; an ability to check |
Plugin methods: fieldTypes(), withoutFieldTypes(), resource(), withoutResource(), navigationGroup(), navigationIcon(), navigationSort(), navigationBadge(), authorize().
Documentation
Installation · Building forms · Rendering · Submissions · Spam protection · Extending · Configuration
Testing
composer test
Changelog
See CHANGELOG.
Security
Report vulnerabilities to support@packstub.dev rather than the issue tracker.
Credits
License
The MIT License (MIT). See LICENSE.
