Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Form Laravel Package

adamwathan/form

A standalone PHP form builder with optional Laravel integration. Build forms through an expressive, fluent API with automatic data binding, validation error display, and customizable markup, keeping views clean and testable.

Deep Wiki
Context7

Getting Started

This is a standalone HTML builder with no framework coupling — no service provider, no facade, no config file. That is the whole point of it, and also the first thing to understand: it is a string generator, not a Laravel form package.

Install it and look at src/FormBuilder.php first, then src/Elements/. Everything you will ever call is a method on FormBuilder that returns an element object; the element object is stringable, so {{ $form->text('name') }} in Blade just works.

composer require adamwathan/form

Because there is no auto-discovery, register a singleton yourself if you want one instance per request (useful so bind() and default values carry across a partial):

// AppServiceProvider::register()
$this->app->singleton(\Adamwathan\Form\FormBuilder::class);

First use case — a create form in a controller/Blade pair:

$form = app(\Adamwathan\Form\FormBuilder::class);
{!! $form->open()->action(route('users.store'))->method('POST') !!}
{!! $form->token() !!}
{!! $form->label('name', 'Name') !!}
{!! $form->text('name')->class('form-control') !!}
{!! $form->email('email')->class('form-control') !!}
{!! $form->password('password')->class('form-control') !!}
{!! $form->submit('Create')->class('btn btn-primary') !!}
{!! $form->close() !!}

If that feels like writing Rails helpers inside Blade, you have understood the package correctly.

Implementation Patterns

Build once, reuse per request. The builder accumulates bound data and defaults. Instantiate it in the controller and pass it to the view rather than newing it inside a Blade loop, otherwise model binding and defaults silently reset.

Bind for edit forms. This is the single feature worth adopting the package for:

public function edit(User $user)
{
    $form = app(FormBuilder::class)->bind($user);

    return view('users.edit', compact('form'));
}

Once bound, $form->text('name') populates from $user->name, and $form->select('role') will pre-select the matching option. Binding accepts an object or an array, and works with Eloquent models because it just reads public properties / array keys — no interfaces, no contracts.

Chain attributes on the element, not the builder. Every element exposes a fluent attribute API: class(), id(), placeholder(), required(), readonly(), disabled(), data('key', 'value'), and a generic attribute('name', 'value'). Build small private helpers for your design system so you write ->class('form-control') in one place:

private function input($name, $type = 'text')
{
    return $this->form->{$type}($name)->class('form-control')->id("field-{$name}");
}

Default values as a fallback layer. ->defaultValue('x') applies when nothing is bound and no value is set. Use it for create forms that share a view with the edit form; the precedence is value → bound model → default.

Old input is your job. The builder predates any Laravel-aware repopulation, so bridge it explicitly in a base controller or middleware:

protected function repopulate(FormBuilder $form): FormBuilder
{
    foreach (session()->getOldInput() as $key => $value) {
        // set value on the matching element, or keep a keyed map and apply per field
    }
    return $form;
}

In practice most teams just do ->value(old('name', $user->name ?? null)) per field, which is explicit and debuggable — arguably better than the magic anyway.

Wrap it in a Blade component or your own facade if you keep it. A <x-form.input name="email"> component that internally calls the builder means you can swap the implementation later without touching 60 views — important for a package archived in 2017.

Extension point: subclass FormBuilder. It resolves element classes and exposes its element methods directly, so adding $form->tel(...) or a custom <x-field> wrapper is a small subclass plus one method. Forking is realistic here: it is a handful of files under MIT, and vendoring it is faster than negotiating with an unmaintained dependency.

Integration tip: since output is raw HTML, always use {!! !!} for elements and {{ }} for user-supplied content. Never echo an unescaped value into ->class() or ->attribute() from request input.

Gotchas and Tips

  • Archived since 2017. No PHP 8.x guarantees, no typed properties, no str_contains-style updates, no security patches. Run it on a modern PHP version and expect deprecation notices from untyped property access. If it is load-bearing, fork it — the surface area is tiny.
  • Not a Laravel package. No service provider, no facade, no config/form.php, no Blade directives. Any "Laravel integration" you remember from blog posts was glue code someone wrote by hand.
  • token() is not @csrf. Do not assume the token element reads Laravel's session or the XSRF-TOKEN cookie. Verify what it actually emits on your version, or just use @csrf and treat the form builder purely as a field renderer.
  • Method spoofing is manual. ->method('PUT') sets the method attribute, which is useless in a browser form. Pair it with an explicit _method hidden input ($form->hidden('_method')->value('PUT')) — and remember open()->method() itself only accepts GET/POST semantics in HTML.
  • Escaping is per-element and value-only. Attribute values, labels, and option text go through the element's own escape routine; anything you pass as a raw string to attribute() is your responsibility. Audit once.
  • Don't trust the fluent setter names. Whether ->class('x') replaces or appends, and whether there is a separate addClass, differs across these old builders. Dump the rendered string — var_dump((string) $form->text('name')->class('a')->class('b')) — before you build a design system on top of it.
  • Stringable, so debugging is trivial. (string) $element calls render(); log that instead of digging through the DOM. This is the fastest way to answer any "why is this attribute missing" question.
  • Select options need explicit handling. Pass an associative array (['us' => 'United States']) and check whether it expects keys, values, or a collection; old builders frequently assume arrays only, so ->pluck('name', 'id')->all() before handing over.
  • Checkbox/radio binding is the usual footgun. A bound boolean will not necessarily produce checked unless the element compares against the exact value you set. Set ->value(1) explicitly and confirm the rendered HTML.
  • Opportunity is low, not zero. The realistic value here is (a) prototyping speed, (b) reading it as a reference implementation for how Adam structured fluent HTML builders, and (c) a two-hour fork. For production Laravel in 2024+, prefer Blade components or a maintained form package; keep this only if it is already deeply embedded, and put a wrapper in front of it so the next migration is a one-file change.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core