Packstub.

Submissions

Every accepted submission is stored (unless the form's Store submissions is off) with:

Column Content
data The values, keyed by field key, normalised by the field type
fields The label and type of every field at submission time
user_id The logged-in user, when there was one
ip, user_agent Optional (submissions.store_ip, store_user_agent)
source_url The page the form was on
channel web, json, livewire or code
read_at Set when someone opens it in the panel

In the panel

The Submissions relation manager on the form's edit page lists them newest first with a summary of the first values, the page and the channel. Opening one shows every value (copyable) and the details, and marks it read. A filter shows unread ones; bulk actions mark as read, export or delete. Export CSV downloads the filtered list: the form's current fields first, then any key an older submission still has, then the page, IP and user.

The Forms navigation item shows the unread count (navigationBadge(false) to hide it).

Notifications

Addresses in Notify by email each get one email per submission, listing the values and linking to the panel. Emails are queued when submissions.queue_notifications is on and a queue is configured.

Events and sinks

Packstub\FormBuilder\Events\SubmissionReceived is dispatched with the form, the submission and the context; SpamDetected when a submission is dropped.

A sink receives every accepted submission:

use Packstub\FormBuilder\Contracts\SubmissionSink;
use Packstub\FormBuilder\Models\FormSubmission;

class PostToWebhook implements SubmissionSink
{
    public function handle(FormSubmission $submission): void
    {
        Http::post(config('services.hooks.forms'), [
            'form' => $submission->form->slug,
            'values' => $submission->data,
            'page' => $submission->source_url,
        ]);
    }
}

Register sinks in the config (sinks) or at runtime (FormBuilder::sink(PostToWebhook::class)). When the form does not store submissions, the sink receives an unsaved model with the form relation loaded.

From code

use Packstub\FormBuilder\Facades\FormBuilder;

$result = FormBuilder::submit('contact', ['email' => 'ada@example.com', 'message' => 'Hi']);
$result->submission; // FormSubmission

Submissions from code skip the honeypot and time-trap checks; validation still runs and throws ValidationException.