watson/bootstrap-form
watson/bootstrap-form is a Laravel package that adds Bootstrap-friendly form builder helpers for labels, inputs, errors, and validation states, making it easier to build clean, accessible forms with less repetitive markup.
Install the package alongside the Laravel form builder it extends (the dependency changes across releases — check the tag you install):
composer require watson/bootstrap-form
Register the provider and facade in config/app.php (this package predates auto-discovery, so this manual step is required):
'providers' => [
// ...
Watson\BootstrapForm\BootstrapFormServiceProvider::class,
],
'aliases' => [
'BootstrapForm' => Watson\BootstrapForm\Facades\BootstrapForm::class,
],
Publish the config so you can see what is actually being generated:
php artisan vendor:publish --provider="Watson\BootstrapForm\BootstrapFormServiceProvider"
Open config/bootstrap-form.php first — this is the source of truth for the wrapper classes (form-group, form-control, control-label, has-error, help-block), and it is where you confirm the option keys the builder accepts in your version.
First use case — a create form. Use the package facade rather than Form:: so you always hit the Bootstrap-aware builder:
{!! BootstrapForm::open(['route' => 'users.store']) !!}
{!! BootstrapForm::text('name') !!}
{!! BootstrapForm::email('email') !!}
{!! BootstrapForm::password('password') !!}
{!! BootstrapForm::submit('Create user', ['class' => 'btn btn-primary']) !!}
{!! BootstrapForm::close() !!}
Each call returns a complete form-group with <label>, the control, and (when validation fails) a help-block error span. open() handles CSRF and method spoofing for you.
Model binding for edit forms. Use model() so values and selected/checked states come from the model, and spoof the verb for updates:
{!! BootstrapForm::model($user, ['route' => ['users.update', $user], 'method' => 'PUT']) !!}
{!! BootstrapForm::text('name') !!}
{!! BootstrapForm::select('role_id', $roles) !!}
{!! BootstrapForm::textarea('bio') !!}
{!! BootstrapForm::submit('Save changes', ['class' => 'btn btn-primary']) !!}
{!! BootstrapForm::close() !!}
Share one partial between create and edit. Because the builder returns a string, the same partial works for both: start the form in the view, include the fields partial, close the form. Keep the submit button out of the partial if labels differ.
Selects with a placeholder. Merge an empty key into the options array:
{!! BootstrapForm::select('role_id', ['' => 'Choose a role'] + $roles->pluck('name', 'id')->all()) !!}
Checkboxes and radios take Laravel's signature (name, value, checked, options), so the checked flag has to be passed explicitly:
{!! BootstrapForm::checkbox('terms', 1, old('terms', false), ['label' => 'I accept the terms']) !!}
{!! BootstrapForm::radio('plan', 'monthly', true, ['label' => 'Monthly']) !!}
{!! BootstrapForm::radio('plan', 'yearly', false, ['label' => 'Yearly']) !!}
File uploads need files => true on the opening tag; the field itself is just BootstrapForm::file('avatar').
Validation display is automatic via $errors. The builder reads the shared $errors ViewErrorBag and adds the configured error class plus the message. In Laravel 5.5+ this is shared for you; on 5.0–5.4 ensure ShareErrorsFromSession runs in the relevant middleware group. Controllers then just use $this->validate() or a FormRequest.
Old input. The builder routes values through old($name, $value) for standard inputs, so failed validation repopulates automatically — as long as you redirect back with input (Laravel's validate() does this).
Extension points for anything it does not cover. Register macros on the builder (in a service provider's boot) for custom controls, or subclass the builder and rebind it in the container if you need to change how a whole field type is rendered:
BootstrapForm::macro('currency', function ($name, $value = null, $options = []) {
return BootstrapForm::text($name, $value, array_merge(['class' => 'form-control text-right'], $options));
});
Layout overrides. For horizontal/BS3 .form-horizontal layouts, either edit the published config classes globally or pass per-field options (class, label, and — depending on version — help) so individual fields deviate from the defaults without forking the package.
is-invalid / invalid-feedback, not has-error / help-block. Override via config/bootstrap-form.php, but verify the builder does not also emit BS3-only markup in the label/control wrapper.$errors fails silently. If no error bag is shared, the form looks fine but shows no messages. Dump {{ $errors }} (or dd($errors) from the view) before hunting through the package.addresses[0][city] are looked up verbatim; the builder will not resolve the dotted/bracketed error key unless your version normalises it. Use flat names, or render that field manually and call $errors->first('addresses.0.city') yourself.boolean / accepted.old() is intentionally skipped (or ineffective) for password inputs; do not assume a stale-password bug.php artisan config:clear (or config:cache) — otherwise the old form-group/control-label values stick around and look like a package bug.form, making plain Form::text() Bootstrap-aware; the BootstrapForm alias is the safer, explicit choice. Confirm by inspecting the provider's register().{{ BootstrapForm::text('name') }} (escaped) shows you the generated markup and the options actually applied — much faster than reading the source.label and help are not consistent across releases; treat the published config and the package's builder class as authoritative instead of relying on a blog post from the Laravel 4 era.How can I help you explore Laravel packages today?