---
title: "Validating & submitting — Forms for Laravel | Roundly"
description: "Find a form, validate request input against each field’s rules, and store a submission for every field under one reference uuid."
url: https://roundly-consulting.com/open-source/docs/forms-for-laravel/submitting
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Forms for Laravel](https://roundly-consulting.com/open-source/docs/forms-for-laravel.md)

# Validating & submitting

The Forms facade resolves a form with its ordered groups and fields, validates request input against each field’s rules, and stores submissions:

```php
use RoundlyConsulting\Forms\Facades\Forms;

// Eager-loads groups and fields, ordered by their `order` column.
// Throws RoundlyConsulting\Forms\Exceptions\FormNotFoundException for an unknown key.
$form = Forms::find('contact');

// Validate request input — throws Illuminate\Validation\ValidationException on failure
// (a broken field rule and a value that fails its field's type alike). Returns the validated data.
$validated = Forms::validate(form: $form, request: request());

// Persist a submission for every field. Returns a SubmissionResult.
$result = Forms::submit(
    form: $form,
    request: request(),
    sender: auth()->user(), // any Eloquent model, or null
);

$result->uuid;        // shared reference id for this submission
$result->fieldCount;  // number of fields stored
$result->submittedAt; // CarbonInterface timestamp
```

submit() stores; it does not validate. Call validate() first — it runs Laravel’s validator with each visible field’s rules (using field names as attribute labels and your custom messages), then type-checks the values. Both failures throw the same ValidationException, so Laravel redirects back with errors either way. A typical controller:

```php
use Illuminate\Http\Request;
use RoundlyConsulting\Forms\Facades\Forms;

final class ContactFormController
{
    public function store(Request $request)
    {
        $form = Forms::find('contact');

        // Throws ValidationException (redirect back with errors) on failure.
        Forms::validate($form, $request);

        $result = Forms::submit($form, $request, $request->user());

        return back()->with('reference', $result->uuid);
    }
}
```

submit() runs in a transaction: it creates one FormSubmission aggregate and one Submission row per field, all sharing the returned uuid, then dispatches FormSubmitted. A field its conditions hide gets a row with no value — nothing the request sent for it is kept. The sender is any Eloquent model, or null for anonymous submissions.

## Closed forms are rejected

Every path to a final submission rejects a form that isn’t accepting them — a non-public form, or one whose expires\_at has passed — by throwing FormSubmissionClosedException: submit(), draft() (and draftTo()), finalize() and the raw createSubmission(). finalize() checks again, so a draft saved while the form was open can’t be finalized after it closes. Pass bypassClosed: true for trusted internal or admin submissions:

```php
use RoundlyConsulting\Forms\Exceptions\FormSubmissionClosedException;

try {
    Forms::submit($form, request(), $user);
} catch (FormSubmissionClosedException $e) {
    // the form is not public, or its expires_at has passed
}

// trusted internal/admin submissions skip the check
Forms::submit($form, request(), $user, bypassClosed: true);
Forms::draft($form, request(), $user, bypassClosed: true);
Forms::finalize($uuid, bypassClosed: true);
```

## Lower-level API

```php
// Write a single per-field row directly (imports, seeds) — the value is stored as-is.
// A new uuid is generated unless you pass one; rows that share a uuid form one submission.
$first = Forms::createSubmission($nameField, ['value' => 'Ann'], $user, bypassClosed: true);
Forms::createSubmission($emailField, ['value' => 'ann@example.com'], $user, uuid: $first->uuid, bypassClosed: true);

Forms::submission($first->uuid)->get()->values; // ['name' => 'Ann', 'email' => 'ann@example.com']
```

createSubmission() writes one row directly and obeys the closed-form rule unless you pass bypassClosed: true. Rows that share a uuid are filed under one final submission, created by the first of them, so Forms::submission($uuid) reads it and Forms::review($uuid) reviews it like any other. A malformed uuid, or one that names another form’s submission or a draft, throws SubmissionNotFoundException. Prefer an injected manager or the actions? See DI and actions.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/forms-for-laravel.md)
