Appearance
Form Submissions
The built-in Contact form, Form and Newsletter sections store every submission and list it in an Inbox in your panel. Nothing to wire up: drop the section on a page, publish, and messages start arriving.
Upgrading from 1.40 or earlier
The inbox needs the filamentcraft_submissions table. Run php artisan filamentcraft:upgrade to publish and run the new migration. Until you do, submissions are reported and dropped, and filamentcraft:doctor fails the Form submissions check.
What happens on submit
- The section checks the honeypot and the per-visitor rate limit.
- It validates the fields and fires
ContactFormSubmitted,FormSubmittedorNewsletterSubscribed. - The package's listener writes a row to
filamentcraft_submissions(whenforms.storeis on) and mails the addresses informs.notify(when set). - The visitor sees the section's success message.
Storage and mail failures are reported through report() and never reach the visitor: the success state does not depend on your mail server being up.
Each row keeps the site, the published page the section sits on (when it sits on one), the section id, the type (contact, form or newsletter), the submitted fields, the visitor's IP address and browser, and when it was read.
The Form section
The Form section builds a form from field blocks, in any order: Short text, Email, Phone, Number, Date, Long text, Dropdown, Single choice, Multiple choice, Consent and File upload. Each field has a question, a hint, a required switch and a full or half width; choice fields take one option per line. Four ready forms ship as presets: contact, booking request, quote request and return request.
- Validation runs on the server against the fields as published: a choice that is not in the list, a number out of range or a field the form never asked is rejected or ignored. Error messages come in the page's language (all eight bundled locales) and name the field.
- Accessible errors. A summary at the top lists every problem, links to each field and takes focus; each field points at its own message with
aria-describedby. Choice fields sit in afieldsetwith alegend. - Files are checked by type (images, documents or both) and size (up to the field's limit and never past
forms.uploads.max_kb); the file name must match its content's type. Files are stored on the privateforms.uploads.diskunder a random name, attached to the notification mail (up to 20 MB in total) and downloadable from the inbox. They are kept only whileforms.storeis on, and deleting a submission deletes its files. - After sending the visitor sees the thank-you message, or goes to a page you pick.
- The inbox shows the answers under their questions; the first email answer becomes the sender and the mail's reply-to address, and the first long-text answer the excerpt.
FormSubmitted carries formName, answers (each with the field id, label, type and value) and files, so a listener can send a booking to your calendar or a quote request to your CRM.
The inbox
Inbox sits in the FilamentCraft navigation group, with a badge counting unread submissions.
- Filter by type, by read state, and by site when you run more than one.
- Open a submission to read it in full. Opening it marks it read.
- Mark rows read or unread, or delete them, one at a time or in bulk.
The inbox follows the same tenancy rules as the rest of the panel: an operator only ever sees submissions of the sites they can access, and a URL pointing at another tenant's submission 404s. Deleting a site deletes its submissions.
To hide the inbox, or rename it:
php
FilamentCraftPlugin::make()
->withSubmissionsInbox(false) // or config('filamentcraft.forms.inbox')
->submissionsNavigationLabel('Messages');Mail notifications
Set forms.notify to get a plain email per submission. It takes one address, a comma-separated list, or an array:
dotenv
FILAMENTCRAFT_FORMS_NOTIFY="[email protected],[email protected]"The mail is a queued notification sent on demand, so it goes through your queue when one is configured and sends inline otherwise. Contact messages set Reply-To to the visitor, so replying from your mail client answers them directly.
Per-site recipients
forms.notify is one address list for the whole app. In a multi-tenant app each site usually wants its own inbox, so resolve the recipients per site instead:
php
use FilamentCraft\Enums\SubmissionType;
use FilamentCraft\Models\Site;
FilamentCraftPlugin::make()
->formRecipientsUsing(fn (Site $site, SubmissionType $type): string|array|null => $site->owner?->email);Return a string, a comma-separated string or an array. null falls back to forms.notify, and an empty array mails nobody for that site. Invalid addresses are dropped.
Spam defence
Both forms ship with two cheap defences, on by default:
- Honeypot. A hidden field people never see and bots fill in. A filled one gets the success message and is dropped: no row, no mail, no event.
- Throttle. Each visitor (by IP address) may send
forms.throttlesubmissions per minute per form type. The next one shows a "try again in N seconds" error on the form.
php
'forms' => [
'throttle' => 5, // null or 0 turns the limit off
'honeypot' => true,
],Using your own destination
The events always fire, so a listener of your own works alongside the inbox:
php
use FilamentCraft\Events\ContactFormSubmitted;
use Illuminate\Support\Facades\Event;
Event::listen(ContactFormSubmitted::class, function (ContactFormSubmitted $event): void {
Crm::createLead($event->name, $event->email, $event->message);
});When your listener is the only destination you want, turn storage off:
php
'forms' => [
'store' => false, // FILAMENTCRAFT_FORMS_STORE=false
],With storage off and no listener bound, visitors would be told their message was sent while it goes nowhere. filamentcraft:doctor warns about exactly that: Form submissions are discarded, naming each published page that carries an affected section.
Configuration
| Key | Default | Effect |
|---|---|---|
forms.store | true | Write submissions to filamentcraft_submissions and the inbox. |
forms.notify | null | Address, comma-separated list or array to mail per submission. |
forms.throttle | 5 | Submissions per visitor per minute, per form type. null/0 = off. |
forms.honeypot | true | Render the hidden field and drop submissions that fill it. |
forms.inbox | true | Register the Inbox resource in the panel. |
forms.uploads.disk | local | Disk for Form section uploads. Keep it private. |
forms.uploads.max_kb | 10240 | Largest file any upload field accepts, in KB. |
The Submission model can be swapped like any other through filamentcraft.models.submission (see Models & Keys), and its keys follow filamentcraft.database.key_type.