larasell-dev / cushion
Form draft persistence for Laravel: server-backed auto-save for Inertia forms.
v0.4.1
2026-10-12 11:05 UTC
Requires
- php: ^8.3
- illuminate/auth: ^12.0|^13.0
- illuminate/cache: ^12.0|^13.0
- illuminate/console: ^12.0|^13.0
- illuminate/database: ^12.0|^13.0
- illuminate/http: ^12.0|^13.0
- illuminate/routing: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- illuminate/validation: ^12.0|^13.0
Requires (Dev)
- laravel/pint: ^1.22
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^3.0|^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-12 11:08:18 UTC
README
This package auto-saves forms, so users won't lose their data when they accidentally close a tab.
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> ); }