inkvizytor/fluentform
FluentForm is a Laravel package for building forms through a clean, fluent PHP API. Define fields, validation, and submit handling in readable code instead of repetitive HTML, making forms easier to maintain and reuse across your application.
Install
composer require inkvizytor/fluentform
Register (Laravel < 5.5 / no auto-discovery)
In config/app.php:
'providers' => [
// ...
Inkvizytor\FluentForm\FluentFormServiceProvider::class,
],
'aliases' => [
// ...
'Form' => Inkvizytor\FluentForm\Facades\FormBuilder::class,
],
On Laravel 5.5+ auto-discovery usually handles the provider, but the Form alias may still need to be added manually — check php artisan vendor:publish output for a fluentform.php config and publish it (php artisan vendor:publish --provider="Inkvizytor\FluentForm\FluentFormServiceProvider"). That config controls the wrapper/control templates and is the first thing to read.
First use case — render a form in a Blade view:
{!! Form::open()->url('/posts')->method('POST') !!}
{!! Form::text('title')->label('Title')->required() !!}
{!! Form::textarea('body')->label('Body') !!}
{!! Form::submit('Save') !!}
{!! Form::close() !!}
Two rules from day one: every field is rendered through {!! !!} (never {{ }} — the builder returns HTML/HtmlString), and method() handles PUT/PATCH/DELETE by emitting the _method hidden input for you.
If you only have five minutes, look at the package's src/ for the FormBuilder class — the fluent methods (text, select, checkbox, submit, open, close) and their setters (label, value, required, placeholder, class) are the real API surface. The config file only tells you how they render.
Build the form in the controller, render in the view. Composing a form object in a controller (or a dedicated PostForm class) keeps Blade clean and lets you reuse the same form for create/edit:
public function edit(Post $post)
{
$form = Form::open()->url(route('posts.update', $post))->method('PUT');
$form->text('title')->value($post->title)->required();
$form->textarea('body')->value($post->body);
$form->submit('Update');
return view('posts.edit', ['form' => $form]);
}
{!! $form->close() !!}
Reuse via partials or form classes. Because the builder is just chained method calls, extract common field sets into Blade partials or a small plain-PHP class that returns a constructed form. This is the pattern that scales; inline Blade chains become unmaintainable past a few fields.
Use layout helpers. Most FluentForm setups expose open()->horizontal() (or vertical/inline) to switch the surrounding markup. Pick one per project and make it the default in config so all forms stay visually consistent.
Bind validation state manually. There is no automatic old()/$errors binding. Pass values explicitly:
$form->text('title')
->value(old('title', $post->title ?? ''))
->error($errors->first('title')); // if the builder supports it, otherwise render errors yourself
If the builder has no error() helper, render <span class="help-block"> next to the field in Blade — don't fight the package.
CSRF / tokens. Confirm whether the config emits a CSRF token automatically. If it doesn't, call Form::token() (or plain csrf_field()) right after open(). Do not assume the package handles it because open() looks complete.
Custom field macros. If the builder is Macroable, register a Form::macro('markdown', ...) in a service provider's boot() method — not in middleware or a controller — so the macro is available everywhere and only registered once.
Buttons and groups. Group related controls with ->group() / wrapper methods only if you have matching templates in config; otherwise use plain Blade wrappers to avoid markup surprises.
Always use unescaped output. {{ Form::text(...) }} will print escaped HTML. This is the single most common first-day bug.
Era mismatch — verify compatibility early. The description targets Laravel 5 and the last release was March 2022. On Laravel 6+ you may hit facade alias issues, container-binding differences, or Blade/HtmlString quirks. Run a smoke test form on your target Laravel version before adopting; if composer require refuses, check the package's composer.json constraints rather than forcing it with --ignore-platform-reqs (that only hides the problem).
Method spoofing is not free. ->method('PUT') should inject _method, but verify the generated HTML — some versions only set the attribute. Inspect with dd((string) $form->text('title')) when a form "silently" hits the wrong route.
Names with dots/brackets. Array-style names (address[street]) generally work, but confirm escaping and old() key matching; old('address.street') and old('address[street]') behave differently.
Styling is Bootstrap-centric by default. If you're on Tailwind or custom CSS, don't override classes on every field — publish and edit the templates/config once, then keep your Blade calls thin.
Errors are your responsibility. Fields do not read the error bag. Centralize old-input and error rendering in a partial to avoid copy-pasted inconsistencies across forms.
Debugging: cast fields to string ((string) Form::text('x')) or dd() the builder to see what is actually being generated before blaming Blade. When a config change has no effect, confirm you published the config and cleared the config cache (php artisan config:clear).
Extension points: the published config and Blade templates are the intended customization surface; custom macros are the clean way to add fields. Reach into src/ only to understand behavior, but expect breakage on upgrade since the package is effectively unmaintained — treat any edits as a fork.
Pin the version. With 33 stars and no activity since 2022, pin a known-good release in composer.json and isolate form rendering behind your own partials so you can swap the package later without touching every view.
How can I help you explore Laravel packages today?