Search by

larasell-dev / cushion

Haberkamp

Form draft persistence for Laravel: server-backed auto-save for Inertia forms.

Package info

github.com/Larasell-dev/cushion

pkg:composer/larasell-dev/cushion

Statistics

Installs: 251

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.4.1 2026-10-12 11:05 UTC

README

This package auto-saves forms, so users won't lose their data when they accidentally close a tab.

See the full documentation

Installation

Install two packages: one for PHP, the other for JavaScript/TypeScript.

composer require larasell-dev/cushion
npm install @larasell-dev/cushion

Quickstart

Defining forms

use App\Models\User;
use Larasell\Cushion\Form;

final class CheckoutForm extends Form
{
    public function fields(): array
    {
        return [
            'company' => 'string|max:255',
            'first_name' => 'string|max:255|required',
            'last_name' => 'string|max:255|required',
            'street' => 'string|max:255|required',
            'city' => 'string|max:255|required',
            'postcode' => 'string|max:32|required',
            'country' => 'string|max:2|required',
        ];
    }
}

Registering forms

Register the forms inside your web.php file:

use App\Http\Forms\CheckoutForm;
use Larasell\Cushion\Form;

Form::add(CheckoutForm::class);

// Some other route
Route::get(/* ... */);

Rendering forms

Pass the form data from your controller to the client:

use App\Http\Forms\CheckoutForm;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;

public function show(Request $request): Response
{
    $form = new CheckoutForm($request->user());

    return Inertia::render('checkout', [
        'billing' => $form->hydrate($request->user()),
    ]);
}

The last step fills your form with the data from the draft:

import { useFormDraft } from '@larasell-dev/cushion';

function Checkout({ billing }) {
  const { track, set, clear } = useFormDraft('checkout', billing);

  return (
    <form>
      {/* Uncontrolled inputs: spread `track` on */}
      <input type="text" name="company" {...track('company')} />
      <input type="text" name="first_name" {...track('first_name')} />

      {/* Widgets outside the change-event flow: call `set` */}
      <select
        defaultValue={billing.country}
        onChange={(event) => set('country', event.target.value)}
      />

      {/* Discard the stored draft */}
      <button type="button" onClick={() => clear()}>
        Clear draft
      </button>
    </form>
  );
}