yab/formmaker
Yab/Formmaker is a Laravel form builder for creating, rendering, and managing dynamic forms with reusable components, validation, and Blade-friendly templates. Ideal for admin panels and rapid CRUD form development.
Install
composer require yab/formmaker
The package is a thin, opinionated wrapper around the Laravel Html/Form builder, so you almost always need the underlying builder too — laravelcollective/html on Laravel 5.2+ (illuminate/html on 5.0/5.1). v6.0.0 is a major bump, so verify this before anything else: run composer show yab/formmaker and check the dependency tree — a major release frequently accompanies a framework/laravelcollective support bump (or a move away from it entirely). Don't assume the old constraint table still holds; confirm installability against your actual Laravel version in a throwaway branch.
On Laravel ≤ 5.4, register Yab\FormMaker\FormMakerServiceProvider and the FormMaker facade alias in config/app.php; on 5.5+ auto-discovery handles the provider, but confirm the FormMaker alias resolves (php artisan tinker → app('FormMaker')) before writing any views.
Publish and skim the config — twice as important on 6.x
php artisan vendor:publish --provider="Yab\FormMaker\FormMakerServiceProvider"
This drops config/form-maker.php: default template/wrapping style, button/input/label CSS classes, and error-display toggles. That file plus the package's Blade template partials are where 90% of "why does my HTML look like that" answers live — and on v6 it's also where the Bootstrap 5 story is decided. Open it first and note which markup you're actually getting: v6.0.0 advertises Bootstrap 5 compatibility, but the release notes don't say whether that's a new default, a config switch, or swapped partials. Read the config's template/class keys and the README before you write a single view, because the answer changes every class name in your CSS.
If you're upgrading, publish into a scratch directory and diff against your existing config and views rather than overwriting — see the gotchas.
First use case: scaffold a CRUD form in one line
// resources/views/users/create.blade.php
{!! FormMaker::fromTable('users', ['name', 'email', 'password'])
->action(route('users.store'))
->method('POST')
->token()
->render() !!}
fromTable introspects the schema, maps column types to input types (string → text, text → textarea, boolean → checkbox, etc.), and emits labels, inputs and a submit button. That's the package's whole pitch — a working admin form before you've written a single <div class="form-group"> (or, on BS5, <div class="mb-3">).
Where to look first, in order: the GitHub README (it is essentially the full API surface — and the changelog for v6, since the release notes are one line), src/FormMaker.php for the fluent methods, config/form-maker.php for rendering knobs and the Bootstrap 5 settings, and the package's view partials for markup you may want to override.
Three entry points, one fluent object
FormMaker::fromTable('users', [...cols]) — schema-driven scaffolding for create screens.FormMaker::fromObject($user, [...cols]) — same, but values are hydrated from a model; the natural choice for edit/update screens.FormMaker::create() — blank slate; chain field calls manually (->text('name', $options), ->select('role', $options), ->textarea(...), ->checkbox(...), ->file(...), ->submit('Save')) when the schema shouldn't drive the UI.All three return the same chainable object and end in ->render().
Layout variants instead of hand-rolled markup
->asDiv() (the default), ->asUl(), and ->asTable() change only the wrapping structure — useful when dropping a form into a legacy admin theme. ->columns([...]) controls per-field grid widths. Pick one variant per project and standardize; mixing them across views makes CSS overrides miserable. On v6, also pin down which Bootstrap generation you're targeting and lock it in config, so a later patch release can't silently change your wrapper classes.
Model-bound edit forms
{!! FormMaker::fromObject($post, ['title', 'slug', 'body', 'published_at'])
->action(route('posts.update', $post))
->method('PUT')
->token()
->withValidation()
->render() !!}
withValidation() reads the $errors bag and adds error classes plus inline messages next to the matching field. It keys strictly off the input name, so keep your validation rule keys and field names identical. Preserve submitted input on a failed store with ->values(old()) (or old() merged into your model data) — without it, users lose their typing on validation failure.
Stay current with the error classes too. Bootstrap 5 renamed the error-state idiom (has-error → is-invalid, help-block → invalid-feedback, control-label → form-label). If v6 handles that for you, great — verify it in the rendered output of a deliberately failing form, not just in the config file. If it doesn't, that's the first set of class overrides to write.
Staying inside your own Blade layout
render() returns an HTML string, so it belongs inside {!! !!} and nothing else. If you need to interleave custom markup, render the form object in pieces or fall back to ->create() plus individual field calls so you keep control of ordering.
Integration tips
UserForm, PostForm) rather than sprinkling facade calls across controllers — it makes the eventual migration to Blade components, and any future major-version upgrade, a single-file change per form.->withAngular() / ->withAngularBootstrap() add ng-model bindings for you, which is the fastest way to retrofit a legacy AngularJS admin without rewriting views. Check these survived the v6 rewrite — they're the most likely thing to be quietly dropped from a modernized release.form-control-equivalent class names without touching templates.Bootstrap 5 support is the headline, and it's also the trap. v6.0.0 ships BS5 compatibility, but the release notes are a single line — treat the details as unknown until you've read the changelog and the published config. Concretely: (a) if your app is still on BS3/4, the v6 defaults may now emit BS5 classes that match nothing in your stylesheet — check for a config toggle to pin the old markup; (b) if you're on BS5, the old advice (form-group, has-error, control-label) is obsolete and you should confirm the new output uses mb-3, is-invalid, invalid-feedback, form-label; (c) published views from a previous version will override v6's partials, so your "upgraded" install can keep rendering BS3 markup indefinitely. Re-publish to a temp dir and diff before overwriting.
A major version means breaking changes — audit before you upgrade. Pin the exact constraint in composer.json, upgrade on a branch, and open one form of each shape (fromTable, fromObject, create(), each layout variant) against your real CSS. Method renames, dropped fluent helpers, and changed defaults are all on the table at 6.0.0 and none of them are documented in the release note you have.
Maintenance picture may have improved — verify, don't assume. A v6 release implies someone is actively maintaining this again, which softens the old "last release February 2022" concern. But 37 stars and zero dependents means very little external testing, and the historical weak point was the abandoned laravelcollective/html. Check whether v6 still depends on it; if it does, the dependency risk is unchanged, and on Laravel 10+ you should still expect conflicts. For greenfield work, Blade components or spatie/laravel-html remain the lower-risk choice.
render() does not escape. It's raw HTML by design. Values flowing through the underlying Html builder are escaped, but any option array you pass (attributes, select labels, help text) is your responsibility. Treat {!! !!} here as you would any hand-written markup.
Schema introspection needs a live DB connection at render time, which means fromTable calls can fail in tests, in queue contexts, or with a cold connection. Prefer fromObject/create() in anything that isn't a straightforward web request, and cache nothing — the introspection runs on every render.
Type inference is best-effort. Enum and JSON columns, unsigned tinyints used as booleans, and nullable dates rarely map to the widget you want. Treat generated fields as a starting point and explicitly override anything ambiguous; you'll usually end up calling ->select() by hand on at least one column per table.
Password fields on edit screens are a classic footgun: fromObject will happily populate a password input from a model attribute. Never bind a password column to a value — build that field via create() and leave it empty, or the hashed string shows up in the DOM.
CSRF and method spoofing. ->token() emits the hidden _token; ->method('PUT'|'PATCH'|'DELETE') emits the _method hidden input. If you drop either for a plain <form> you must supply @csrf yourself. File uploads additionally need enctype="multipart/form-data" — check whether your installed version exposes an ->files(true)-style flag, otherwise set it via the form attributes. Re-verify these helpers exist under their old names after the v6 bump.
Nested field names break error matching. withValidation() matches on the literal name attribute; names like address[line1] or items[0][sku] typically won't line up with Laravel's address.line1 error keys. For nested shapes, render fields manually and surface $errors yourself.
Config caching. After editing config/form-maker.php always run php artisan config:cache (or config:clear while iterating) — stale cached config presents as "my class changes did nothing." This bites hardest immediately after a version upgrade, when both the config and the underlying defaults have changed.
Debugging. dd($form->render()) gives you the exact string; more often the bug is a missing facade registration, an unpublished config, or — post-upgrade — a stale published partial shadowing the new one. When markup looks wrong, diff your published views against the vendor copies before blaming your overrides.
Don't over-reach. This is a server-side string builder. Conditional/dynamic fields, per-row repeaters, and async validation are Livewire or Alpine territory. Use FormMaker for the boring 80% of CRUD forms and drop to hand-written Blade for the interesting 20%.
How can I help you explore Laravel packages today?