netojose/laravel-bootstrap-4-forms
Laravel Bootstrap 4 Forms offers an elegant form builder for generating Bootstrap 4-styled forms in Laravel. It handles model binding, validation errors, old input, and common field types, cutting boilerplate markup while keeping your Blade views clean and readable.
Install via Composer:
composer require netojose/laravel-bootstrap-4-forms
On Laravel 5.5+ the service provider and Form facade are auto-discovered. On older versions add to config/app.php:
Netojose\LaravelBootstrap4Forms\LaravelBootstrap4FormsServiceProvider::class,
// aliases
'Form' => Netojose\LaravelBootstrap4Forms\Facades\Form::class,
Make sure your layout actually loads Bootstrap 4 CSS/JS — the package only emits markup with BS4 classes, it does not ship assets. Publish the config if you want to tweak wrapper/error classes:
php artisan vendor:publish --provider="Netojose\LaravelBootstrap4Forms\LaravelBootstrap4FormsServiceProvider"
Where to look first
config/laravel-bootstrap-4-forms.php — the class strings applied to form groups, inputs, error states. This is your main customization lever.src/Form.php (and any FormBuilder/view files) — the full list of available methods and their signatures. The README covers the common ones; the source is the source of truth for the rest.resources/views/ — the Blade partials that render each control. Publish/override these if you need different markup.First use case — a simple create form:
{!! Form::open(['url' => route('posts.store'), 'method' => 'POST']) !!}
{!! Form::text('title', 'Title') !!}
{!! Form::textarea('body', 'Body', ['rows' => 5]) !!}
{!! Form::select('status', 'Status', ['draft' => 'Draft', 'published' => 'Published']) !!}
{!! Form::submit('Save') !!}
{!! Form::close() !!}
open() emits the CSRF token and method spoofing automatically, so most teams start by swapping hand-written <div class="form-group">…</div> blocks for these calls one form at a time.
Model binding for edit forms. Rather than manually computing old()/model-value fallbacks per field, pass the model into open():
{!! Form::open(['model' => $post, 'url' => route('posts.update', $post), 'method' => 'PATCH']) !!}
{!! Form::text('title', 'Title') !!}
{!! Form::textarea('body', 'Body') !!}
{!! Form::close() !!}
The package resolves each field's value from old('field') first, then the bound model attribute. This is the single biggest time-saver — it removes the old('title', $post->title ?? '') boilerplate from every input and keeps create/edit partials identical.
Validation feedback comes for free. Because the package reads the shared $errors bag (populated by ShareErrorsFromSession in the web middleware group), a failed FormRequest validation automatically adds is-invalid to the control and renders an invalid-feedback block. You get the Bootstrap 4 error styling without touching your Blade.
One partial for create and edit. Combine model binding with a shared _form.blade.php:
{{-- posts/_form.blade.php --}}
{!! Form::text('title', 'Title', ['required' => true]) !!}
{!! Form::select('status', 'Status', $statuses) !!}
Then create.blade.php and edit.blade.php differ only in the open() call. This is the pattern most teams settle on.
File uploads. Add 'files' => true to the open() options so the enctype is set for you:
{!! Form::open(['url' => route('media.store'), 'files' => true]) !!}
{!! Form::file('image', 'Image') !!}
{!! Form::submit('Upload') !!}
{!! Form::close() !!}
Checkbox/radio for boolean and enum fields. Checkboxes take a value and a checked state; radios take an options map plus the selected key. Bind them through the model so "was it checked?" is derived from the record:
{!! Form::checkbox('is_published', 'Published', 1) !!}
{!! Form::radio('visibility', 'Visibility', ['public' => 'Public', 'private' => 'Private']) !!}
Attribute passthrough for everything else. The final array on each helper is passed straight to the underlying input, so placeholder, required, autocomplete, id, data-* and custom classes all work. For bespoke layout you can still hand-write the surrounding markup and only use the helpers for the control itself.
Composition in larger forms. For long forms, extract repeated field groups (address blocks, line items) into partials that take a $prefix/$index and call the helpers with prefixed names. Because the helpers are plain static calls, they compose fine inside Blade @foreach/@include.
{!! !!}, not {{ }}. The helpers return HTML strings; escaping them with {{ }} prints markup as text. Only label/value content you pass in is escaped by the package — never concatenate raw user input into a label expecting safety.laravelcollective/html, both register a Form alias. Alias this one differently (e.g. BsForm) or import the full namespace in the file you use it in.form-group, custom-select, custom-control custom-checkbox, invalid-feedback inside a form-group. On BS5 these no longer map to the same styling. Either stay on BS4 or publish and rewrite the package's views/config to emit mb-3, form-select, form-check, etc.composer.json constraints may prevent installation on Laravel 9/10/11. Check before committing to it in a new project; if you must use it, fork and relax the constraints, or vendor the Form class into your app. For a 2024+ codebase, native Blade components or a maintained alternative are the safer bet.old() beats the model. Binding a model does not override a flashed old value — which is what you want, but it surprises people debugging "why isn't it showing the DB value?" after a failed submit.old('is_published') is null and the model's 0/false should be relied on. Make sure your checkbox value and the model cast agree (boolean cast on the model avoids "0" vs false confusion).select with multiple, name the field roles[]; otherwise only the last selection is submitted.{{ dd(Form::text('foo', 'Foo')) }}) to see exactly which classes are emitted, then compare with the config. If validation styling isn't appearing, confirm the request goes through the web middleware group (so $errors is shared) and that you're rendering the form after a redirect-back, not in an API route.$response->assertSee('name="title"', false) and assertSee('is-invalid', false) in feature tests cover most of what you need. Assert on attribute presence rather than exact class strings, which are easy to reformat.How can I help you explore Laravel packages today?