wotz/laravel-livewire-forms
A Laravel package for building and managing forms with Livewire, helping you wire up form components, validation, and stateful UI without repetitive boilerplate. Streamlines common form workflows so you can focus on your app's logic.
Install and verify compatibility first. The package is a thin Livewire form abstraction, so its constraints are your constraints:
composer require wotzebra/laravel-livewire-forms:^5.1
v5.1.0 adds PHP 8.5 / Laravel 13 support, so the floor has moved. Confirm the Livewire major it targets in composer.json (livewire/livewire: ^3.0 is the safe assumption for a 2026 release) and the PHP/Laravel floor before writing code. Pin to ^5.1 deliberately: the 5.x line is now the maintained line, and you want the Laravel 13 compatibility commit rather than an arbitrary earlier 5.x.
Where to look first, in order:
php artisan boost:install your AI assistant gets package-authored, version-matched guidance about the form API. Read that file yourself too — it's typically the most concise, current description of the API surface (base class name, rules(), messages(), lifecycle hooks) and it's maintained by the author rather than inferred.README.md — the author's own happy path. Small packages like this usually have exactly one supported workflow; find it and stay inside it.src/ — look for the base form class (typically Form, BaseForm, or a HasForm trait). That file is the entire API surface: property hydration, rules(), messages(), submit(), and any lifecycle hooks. src/ wins over the Boost guidelines whenever they disagree — AI guidelines can lag a release.config/ — if a config file is publishable, it usually controls validation message namespacing, view paths, or auto-binding behaviour.First use case: a single-resource create/edit form (a Post, a Profile, a support ticket). Do not start with multi-step wizards or nested repeaters — those are where thin abstractions break, and you want to learn the package's serialization rules on something small.
php artisan boost:install # pull in the packaged AI guidelines
php artisan vendor:publish --tag=livewire-forms-config # if it exists
Build one form, submit it, and assert it with Livewire::test() before committing to the pattern across the app.
Form object as a public property. The standard shape is a plain PHP class holding your fields, exposed on the component as a public property so Livewire can hydrate it each request:
class ContactForm extends Form // exact base class name: check src/
{
public string $name = '';
public string $email = '';
public string $message = '';
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:120'],
'email' => ['required', 'email'],
'message' => ['required', 'string', 'min:20'],
];
}
}
class ContactPage extends Component
{
public ContactForm $form;
public function mount(): void
{
$this->form = new ContactForm();
}
public function submit()
{
$this->form->validate();
Contact::create($this->form->toArray());
$this->form->reset();
$this->dispatch('contact-sent');
}
}
Keep validation in the form, orchestration in the component. The value of a form package is co-locating rules with fields so the same form can be reused by an HTTP controller, a Livewire component, and a seeder. If you start putting rules in the component, you've paid the dependency cost for nothing.
Let Boost carry the conventions. Rather than re-teaching your team (or your AI assistant) the form API in every prompt, rely on the packaged skills to scaffold new forms. The practical workflow: ask the assistant to generate a form extending the package base class, then diff its output against src/. Anything the assistant invents that isn't in src/ — a save() helper, a different hook name — is hallucination, not API.
Model binding pattern. For edit screens, the usual idiom is a fill(Model $m) / toArray() pair on the form class. Bind in mount(), and save by mapping back explicitly rather than mass-assigning — mass assignment from a form object is how you accidentally expose is_admin.
Reuse via a Blade partial. Render the form fields in resources/views/livewire/forms/contact.blade.php and @include it wherever needed. This is the main practical payoff: one field definition, used in a modal, a full page, and a test.
Compose with Livewire actions. Emit events on success ($this->dispatch('saved')) and let the parent page close the modal or refresh a table, rather than having the form class reach into UI concerns.
Nested data / repeaters. If the package supports array fields, treat them as an add-on: public array $items = [['sku' => '', 'qty' => 1]] with dot-notation rules (items.*.sku). Verify hydration survives a re-render before building a whole UI on it.
Request instances, no raw Eloquent models inside the form object on a subsequent request — Livewire round-trips public state through the payload. Store IDs or scalars and re-resolve in mount() or the action.wire:model timing is the #1 source of "it doesn't validate" bugs. wire:model (deferred) won't update on blur; wire:model.live fires every keystroke and will hammer your updated() hooks. Use .blur or .live.debounce.500ms for text, .live for selects/checkboxes.WithFileUploads on the component. The form object cannot make uploads work on its own; the trait must be on the Livewire component, and the temp file property must live where Livewire can see it (usually on the form object, but the trait on the component).submit() in a $this->lock guard or disable the button with wire:loading.attr="disabled" plus wire:target="submit".reset() vs. $this->reset(). If the form class provides its own reset(), it may not reset validation errors. Call the package's reset and $this->resetValidation() (or confirm the package does it) — otherwise stale error bags persist after a successful save.messages() method on the form class or a validation.php translation key the package namespaces. If neither exists, pass messages via the validate($rules, $messages) call and keep them out of the form class.beforeSave/afterSave, or an overridable submit(). If you need something the package doesn't expose, subclass the base form rather than forking — subclassing survives composer update, forking does not.src/ and the changelog, not the guidelines. Also re-run boost:install after upgrading the package, or you'll be coding against v5.0 guidance on a v5.1 install.composer.json before assuming the upgrade is free.composer.json, and keep your form classes free of package-specific logic beyond the base class and rules(). If the package disappears, swapping to a hand-rolled form object should be a one-file change per form.Livewire::test(...)->payload()) and check whether a typed property is receiving a mismatched type — a string property fed null from an empty input is the classic failure, and PHP 8 typed properties will throw rather than coerce.Livewire::test(ContactPage::class)->set('form.email', 'bad')->call('submit')->assertHasErrors('form.email') — note the form. prefix, which trips people up when copying rules from a controller.How can I help you explore Laravel packages today?