Back to Blog
4 min read

From Dynamic to Deterministic: Refactoring Form Resources in Laravel with Filament

The Problem with Dynamic Form State

At AustinsElite, we’ve spent months refining our user-facing forms—especially the request-quote flow, one of our most critical touchpoints. Originally, this form relied heavily on dynamic state handling in the frontend, with loose data shaping passed to a Laravel backend. While flexible at first, this approach became a liability as complexity grew.

We were managing form state across Livewire components and API endpoints with minimal structure. Field validation was scattered—some enforced in JavaScript, some only on the server—and inconsistent payloads meant edge cases crept into submissions. Debugging invalid entries felt like chasing ghosts. We needed stronger contracts between frontend and backend.

The real pain point? Uncertainty. Was that service_type field a string or an array? Did the frontend send null or omit the key entirely when empty? These ambiguities led to defensive coding, extra null checks, and occasional 500 errors from malformed input.

We weren’t alone—this is a common trap when building rich forms in Laravel apps with decoupled frontend logic. But we realized our dynamic flexibility was costing us reliability.

Enter RequestQuoteResource: Enforcing Structure at the Edge

Our solution wasn’t to add more client-side validation or middleware—it was to shift toward deterministic form handling using a dedicated resource layer. We introduced RequestQuoteResource, a server-side class within our Laravel 12 backend that defines exactly how a quote request must be structured, validated, and transformed before processing.

This wasn’t just a form request—it was a resource object that encapsulates the entire lifecycle: validation rules, data normalization, and even email integration logic. Here’s a simplified version of what it looks like:

class RequestQuoteResource
{
    public function __construct(public array $data) {}

    public function validated(): array
    {
        return Validator::make($this->data, [
            'name' => 'required|string|max:255',
            'email' => 'required|email',
            'service_type' => 'required|in:basic,premium,enterprise',
            'project_details' => 'nullable|string',
        ])->validate();
    }

    public function toNotification(): QuoteSubmitted
    {
        return new QuoteSubmitted($this->validated());
    }
}

Now, every quote submission—whether from a Livewire component, a Filament form, or an external API call—flows through this single, consistent interface. The frontend (Next.js or otherwise) doesn’t need to know business logic; it just needs to send data that conforms to the contract.

This shift moved us from "hope the data is right" to "guarantee the data is right."

Performance, DX, and Fewer Midnight Bugs

The impact was immediate. First, validation became reliable and centralized. No more duplicate rules across JavaScript and PHP. When we update a rule in RequestQuoteResource, it applies everywhere—frontend forms now align automatically because they’re tested against real API responses during integration.

Testing improved dramatically. Instead of mocking complex frontend interactions, we could unit test the resource directly:

/** @test */
public function it_rejects_invalid_service_types()
{
    $resource = new RequestQuoteResource([
        'name' => 'Jane Doe',
        'email' => '[email protected]',
        'service_type' => 'ghost',
    ]);

    $this->expectException(ValidationException::class);
    $resource->validated();
}

We also caught subtle bugs early—like when our frontend sent an empty string instead of null for optional fields. The resource layer normalizes these before they hit business logic, reducing noise in logs and error tracking.

Developer experience? Night and day. New team members can read RequestQuoteResource and instantly understand what a valid submission looks like. No spelunking through Vue or React components to trace state changes. Plus, pairing this with Filament’s form builder let us reuse the same validation rules in admin panels—ensuring internal tools handle data the same way public ones do.

And yes, performance got a quiet boost. Less client-side logic means smaller bundles. Fewer malformed requests means less server-side cleanup. It’s not flashy, but it adds up—especially under load.

Closing: Predictability Over Flexibility

This refactor was part of our June push to stabilize core workflows, including email integration and form validation across Livewire and Filament. Moving away from dynamic, free-form handling to structured resources wasn’t about adding complexity—it was about removing uncertainty.

At AustinsElite, we’re not building prototypes anymore. We’re running high-traffic flows where reliability matters more than clever code. Sometimes, the most powerful refactor isn’t a rewrite in a new framework—it’s choosing determinism over dynamism, one form resource at a time.

Newer post

How We Fixed Form Validation and Data Binding in a Multi-Step Laravel Rental Quote Form

Older post

How We Simplified State Management in a Multi-Step Livewire Form with a Single $step Variable