lexal/symfony-stepped-form
Symfony Stepped Form lets you build multi-step, wizard-style forms in Symfony applications with step navigation, validation, and form state handling for smoother user workflows.
This is a Symfony package, not a Laravel one. Before anything else, confirm what you actually got: read the README's requirements block (PHP constraint, symfony/form version, whether it ships a Bundle class or is a plain library). That single detail determines whether the next ten minutes are trivial or a dead end.
$app->register() it. You would be pulling in symfony/form, symfony/http-foundation, and the DI container piecemeal, or running it inside a small Symfony service container alongside Laravel. Treat this as "probably not worth it" unless you're already doing that.composer require and use it with symfony/form directly. This is the only realistic path in a Laravel app.First use case to target: a 3-step wizard (e.g. account → address → confirm) with per-step validation and a back button that preserves entered data. Build that end to end before touching anything more complex.
Where to look first, in order:
Note that class and method names below are the conventional shape for stepped-form libraries; verify them against the actual README before writing code.
One form type per step, not one giant type. The idiomatic structure is a step-1 type, step-2 type, step-3 type, each bound to the same underlying data object. Each type then owns its own fields and its own validation. This beats a single 40-field type with conditional if ($currentStep === 2) guards, which becomes unreadable by step five.
Persist the data object, not the form. Keep the partially-filled DTO/entity in the session (or an unconfirmed row in the DB) between steps, and re-bind a fresh form to it on each request. If you persist the form state itself you inherit serialization problems and stale CSRF tokens.
Validate per step with validation groups. Map each step to a group:
$form = $this->createForm(AddressType::class, $wizard->getData(), [
'validation_groups' => ['step_address'],
]);
Without this, the step-1 form runs the constraints for every field in the object and step 1 fails because the step-3 email isn't filled in yet. This is the single most common cause of "the wizard won't advance."
Navigation and submit handling belong in one controller action with two branches: an "advance" submit button and a "back" button. Back should not validate — just persist the raw submitted data and render the previous step.
Rendering in Laravel. Symfony form types render via Twig form themes. In Blade you either (a) render each field manually with form_row()-equivalent helpers ported over, or (b) accept the overhead of running Twig for the wizard's partials only. For anything beyond a trivial form, (b) is less painful than it sounds. If you're on Livewire or Inertia, strongly consider whether the multi-step UX you need can be done with plain Blade components and Laravel's validator instead — for most CRUD wizards it can, and you avoid the whole Symfony Forms dependency.
Testing. Because each step is its own form type, you can unit-test the step types in isolation and integration-test the controller with a sequence of POSTs, asserting the session/DTO carries forward. Write that integration test first — it's the cheapest way to catch state bugs.
symfony/form, symfony/options-resolver, symfony/property-access, and a translation dependency to a Laravel app that already has a form/validation stack. The dependency weight is meaningful. Justify it against the alternative.composer.json and don't build abstractions directly against its types — wrap the wizard in a small application-level service so a future break is contained to one file.@cache, response caching, view fragments), the token goes stale and step 2+ fails with a 419. Don't cache wizard steps.Default. If constraints are declared without a groups option they live in Default and will silently run on every step. Annotate each step's constraints with an explicit group at the same time you add the field.How can I help you explore Laravel packages today?