evanschleret/formforge
FormForge is a Laravel package for defining and rendering forms in code. Build reusable form classes with fields, validation, and layouts, then output them in Blade with minimal boilerplate. A practical toolkit for keeping forms organized and maintainable.
FormForge is a Laravel package for defining and managing forms (schemas, fields, submissions) from your application rather than hand-rolling CRUD for each one. Start with the three things that decide whether it fits your app:
Install and publish.
composer require evanschleret/formforge
php artisan vendor:publish --tag="formforge-config"
php artisan vendor:publish --tag="formforge-migrations"
php artisan migrate
Check composer.json constraints first — v2.1.1 raises the minimum supported Laravel version to patched releases, so a stale framework install will block the upgrade (which is the point of the change).
Read the config file before the README examples. In v2.1.1 the management routes are authenticated and gated by default. The config is where the route prefix, middleware stack, and the Gate ability name live; that file is the fastest way to understand what the package will wire into your app.
First use case: put the management UI behind your existing auth. Add the ability to a policy or Gate::define() and confirm an admin user can reach the routes and a normal user cannot. Do this before building any forms — it validates the integration and surfaces the new default in a controlled way instead of in production.
A good first real task is a single low-risk internal form (contact request, feedback, intake survey) so you exercise define → render → submit → persist end to end without touching customer-facing flows.
Define forms in code, not in the database, when the shape is known. Treat the form definition as application config/schema that lives in a class or a dedicated directory, and let the package handle rendering and submission storage. This keeps forms reviewable in PRs and diffable, which is the main reason to choose a package like this over a generic admin panel.
Gate the management plane, not just the routes. v2.1.1's default is authentication + a Gate ability, but the ability is the knob you actually own. A common pattern:
// AuthServiceProvider / AppServiceProvider
Gate::define('manage-forms', fn (User $user) => $user->hasRole('admin'));
Then map that ability in config/formforge.php. If you need finer control (per-form editing rights, tenant scoping), wrap the package's authorization with your own policy rather than replacing the middleware wholesale — you keep future security patches working.
Separate the two audiences in your routing. The management routes registered by the package are for staff; the public form-rendering and submission endpoints are for end users. Keep your own public routes thin — a controller action that resolves a form and returns a view — so your app's middleware (rate limiting, throttling, CSRF, locale) applies to submissions rather than being inherited accidentally.
Treat submissions as domain data, not package data. If submission records are stored in your database, add your own query layer or scopes for reporting, exports, and retention policies. Do not let dashboards query package internals directly — you want a seam when the underlying table or model changes.
Roll out in an environment-scoped way. With the new defaults, local/dev will hit the auth wall too. If you need the management UI unauthenticated in local only, do it explicitly and narrowly in config keyed off the environment, and add a test that asserts the ability is required when APP_ENV=production. Environment-conditional auth is the most common way this class of default gets silently disabled everywhere.
Test the authorization boundaries, not the form markup. The valuable tests are: an unauthenticated request to a management route is rejected; an authenticated user without the ability gets 403; an authenticated user with the ability succeeds; a submission with an invalid payload is rejected. The package owns the rendering; you own the perimeter.
v2.1.1 is a behavioral break inside a patch release. If you upgrade and management routes start redirecting to login or returning 403, that is the new default working as intended — not a misconfiguration. The fix is to define the Gate ability and grant it, or (deliberately) relax the config. Plan the upgrade as a deploy that requires a policy change in the same commit; do not ship the composer bump alone.
Define the ability before you deploy, or the UI goes dark. Because the ability name is only meaningful once something defines it, an upgrade with no Gate::define() leaves even your super-admins locked out. Order of operations: add the gate definition → deploy → upgrade the package, or ship both atomically with a feature flag.
Silent 403s versus redirects. An unauthenticated user typically gets redirected to login, while an authenticated user without the ability gets a 403. If your on-call runbook only knows about redirects, "logged in but forbidden" failures will look like a broken app rather than a policy gap. Log both paths and check laravel.log for the ability name when debugging.
Check the framework patch level, not just the major version. The raised minimum is to patched Laravel releases. A project pinned to an unpatched 10.x or 11.x will fail the constraint with a confusing Composer message about the package rather than about Laravel. Run composer update laravel/framework (or the skeleton packages) before fighting the FormForge constraint.
Cache and config resets after upgrading. Route registration, middleware, and config all change here. php artisan optimize:clear (or at minimum config:clear and route:clear) after deploy — a cached route/config file from the previous version is a classic cause of "the fix didn't apply."
Publishing tags drift between versions. If you published config or migrations on an older release, diff the package's config/formforge.php against yours after upgrading. New security keys added in a patch release will not appear in your already-published copy, which means you can be running the new code with the old configuration surface — and in this release that is precisely the gap that matters.
Extension points worth knowing. Config for route prefix/middleware/Gate ability, the published migrations (submission storage), and any view/asset publishing for rendering. Overriding views is fine for styling; overriding authorization or route registration is where upgrades get painful. Prefer registering your own routes and pointing them at the package's services over editing package-owned middleware.
Watch this package's release cadence. A security default changing in a patch release signals maintainers who will make pragmatic calls about semver. Pin to a version range you review, subscribe to releases, and re-read the changelog on every bump — do not assume ^2.1.0 upgrades are inert.
How can I help you explore Laravel packages today?