Skip to content

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 ​

  1. The section checks the honeypot and the per-visitor rate limit.
  2. It validates the fields and fires ContactFormSubmitted, FormSubmitted or NewsletterSubscribed.
  3. The package's listener writes a row to filamentcraft_submissions (when forms.store is on) and mails the addresses in forms.notify (when set).
  4. 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 a fieldset with a legend.
  • 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 private forms.uploads.disk under a random name, attached to the notification mail (up to 20 MB in total) and downloadable from the inbox. They are kept only while forms.store is 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.throttle submissions 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 ​

KeyDefaultEffect
forms.storetrueWrite submissions to filamentcraft_submissions and the inbox.
forms.notifynullAddress, comma-separated list or array to mail per submission.
forms.throttle5Submissions per visitor per minute, per form type. null/0 = off.
forms.honeypottrueRender the hidden field and drop submissions that fill it.
forms.inboxtrueRegister the Inbox resource in the panel.
forms.uploads.disklocalDisk for Form section uploads. Keep it private.
forms.uploads.max_kb10240Largest 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.