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

Formmaker Laravel Package

grafite/formmaker

Grafite FormMaker is a Laravel package for rapidly building model-driven forms with reusable field types, validation, and relationship-aware inputs. It simplifies form generation and editing for admin panels and CRUD workflows, reducing repetitive Blade markup.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require grafite/formmaker
php artisan vendor:publish --provider="Grafite\FormMaker\FormMakerProvider"

That publishes config/formmaker.php, which is the first file to read: it holds the field-type registry (which PHP class renders text, select, textarea, etc.), the default button set, and the error-display switch. Second stop is vendor/grafite/formmaker/src/Forms/BaseForm.php — that's where model binding, route resolution, and button rendering live, so it answers most "why did it render that?" questions. Third is src/Fields/, which is effectively your API reference for chainable options (label, class, attributes, options, required…).

v6.0.0 is a major version and advertises Bootstrap 5 compatibility. The release note is one line, so before you build anything, re-read the published config for a framework/CSS setting and diff the src/Fields/ templates against what you last used — the markup a field emits is the thing that changed. If you are still on Bootstrap 4, stay on the 5.x line until your CSS is migrated; the upgrade is a visual break, not a runtime error.

Generate your first form class rather than hand-writing it:

php artisan make:form BlogForm
namespace App\Forms;

use Grafite\FormMaker\Forms\BaseForm;
use Grafite\FormMaker\Fields\Input;
use Grafite\FormMaker\Fields\Textarea;
use Grafite\FormMaker\Fields\Select;

class BlogForm extends BaseForm
{
    public $model = \App\Models\Blog::class;
    public $routePrefix = 'blogs';

    public function fields()
    {
        return [
            Input::make('title')->label('Title')->required(),
            Textarea::make('content')->label('Content')->rows(8),
            Select::make('status')->options(['draft' => 'Draft', 'live' => 'Live']),
        ];
    }
}

Register the class under the forms key in the config (so the facade can resolve it by short name), then render it in Blade — and note the unescaped braces:

{!! FormMaker::from('blog') !!}

The canonical first use case is a CRUD create/edit screen for one resource. Get that working end to end (store → redirect → edit → update) before converting any other page; the value of the package is only realised when a form is reused for both store and update, which happens for free via the model binding.

If you are upgrading rather than starting fresh: render one existing form to HTML on 5.x, save it, upgrade, render again, and diff. That single comparison tells you more about v6 than the release note does.

Implementation Patterns

One form class, two actions. Keep a single BlogForm and pass the model when editing (FormMaker::from('blog', ['model' => $blog]) or by instantiating the class with the model), letting $model + $routePrefix derive the action, the spoofed PUT/PATCH method, and the populated values. The moment you fork into CreateBlogForm/EditBlogForm you've lost the main benefit.

Field objects over config arrays. The package supports declaring fields as arrays in config, but chainable field classes co-locate label, validation hints, and layout with the field itself. Standardise on the object style and treat the config fields map purely as a type registry.

Keep fields() free of queries. options() calls that hit Eloquent run on every render, including form re-displays after a validation failure. Either cache the collection in the form class constructor or pass dynamic option sets in through the render options array from the controller.

Layout in $columns. For two-column forms, set the column config on the form class rather than wrapping fields in markup — the package handles the grid classes and you stay portable across CSS frameworks. Under v6 those classes are Bootstrap 5 (row g-3, col-md-6, mb-3), so if you have hard-coded Bootstrap 4 grid wrappers in your Blade partials, budget the migration as part of the upgrade, not as a separate task.

Audit your custom field classes on upgrade. Any field you extended that emits form-group, form-row, custom-select, custom-checkbox, or input-group-append is Bootstrap 4 markup and will silently render unstyled on v6. This is the single largest piece of upgrade work in a mature install, and it fails visually rather than loudly — so grep for those class names across app/Forms and any published views before you trust a staging deploy.

Validation lives outside the form. Pair with a FormRequest and let the package render the error markup (config errors => true, requires the ShareErrorsFromSession middleware). If you prefer @error directives, turn the package's error output off so you don't get duplicates. v6 changes the error/invalid class pairing (is-invalid + invalid-feedback), so re-check that both halves still land on the same element after upgrading.

Extend for the weird fields. Anything the registry doesn't cover (a rich-text editor wrapper, a currency input, a dependent dropdown) becomes a small class extending the base field, registered in config fields. This is the intended extension point and keeps bespoke markup out of your Blade files.

Migrate form by form. Start with the largest, most repetitive form. Leave custom JS widgets as @included partials inside a custom field class until you have time to formalise them. Keep field definitions thin so an eventual swap to Blade components is mechanical.

Gotchas and Tips

  • Read the diff, not the release note. "Bootstrap 5 compatibility and more" is not a changelog. Check the repo for a CHANGELOG/release body, and in the absence of one diff src/Forms/BaseForm.php and src/Fields/ between your locked version and 6.0.0 — the "and more" is where the breaking changes hide. Treat the major bump as breaking by default.
  • Escaping. {{ }} will print the form as HTML-escaped text. It must be {!! !!}, which also means you cannot trust labels/help text sourced from user input.
  • Pin the exact version. "grafite/formmaker": "^6.0" in a fresh install, or an explicit 6.0.0 while you validate the upgrade. Given the thin release note, you want the ability to roll back a minor without re-testing markup.
  • Maintenance signal improved, risk unchanged. A v6 after a multi-year gap suggests the package is being picked up again, but the one-line note means the project's release discipline is still light. Verify the illuminate/* constraints support your Laravel version before composer update drags the package along.
  • License is NOASSERTION. This has not changed with the release. Open the LICENSE file and get it cleared before this ships in anything commercial — don't assume MIT because it's on GitHub.
  • Config caching. After editing config/formmaker.php, run php artisan config:clear. Because forms are resolved by string key → class name, a stale cached config gives you a confusing "class not found" or, worse, renders the old field set. If v6 added a framework/style key, a cached old config will quietly keep you on the old markup — clear it before you judge the upgrade.
  • Published views and assets. If you previously ran vendor:publish and edited the published Blade templates or CSS, v6's new markup will not reach you. Re-publish to a temp path and diff rather than --force over your edits.
  • Route param naming. The generated update/destroy URLs use the resource name derived from $routePrefix. If your route is /admin/posts/{post}/edit but the prefix is blogs, the action silently points at the wrong URI. Check with php artisan route:list.
  • Verify the method spoof. On edit forms, inspect the rendered HTML for the hidden _method input. A missing spoof means your update submits to store and you get a duplicate row instead of an error.
  • old() vs model values. After a failed update, old input should win over the model — confirm this for select (keys come back as strings) and checkbox groups. Checkboxes need explicit handling for the unchecked case, since nothing is submitted at all.
  • File uploads. Set files => true (or the equivalent enctype option) explicitly; the default form tag is not multipart.
  • Don't nest. If you render a FormMaker form inside a Form::open() from laravelcollective/html, you'll get two <form> tags and duplicate CSRF fields. The two packages also both want the Form facade alias — check config/app.php aliases.
  • Debugging recipe: dump($form->fields()) to see the resolved field set, dd($form->formOptions()) for action/method/enctype, then compare against raw HTML with ->toHtml(). Most bugs are an option that never reached the field, not a rendering bug — and after a major upgrade, most styling bugs are a class that reached the field but is no longer a Bootstrap 5 class.
  • Buttons and cancel. The cancel button renders as a link, not a submit — it needs a real URL, and its default derivation is not always what you want on nested admin routes. Set it per form. Button classes are part of the Bootstrap 4 → 5 surface too (btn-default → btn-secondary), so check the default button set in config after upgrading.
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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums