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

Laravel Wizard Laravel Package

smajti1/laravel-wizard

A Laravel package for building multi-step form wizards with dedicated step classes, validation, navigation, and Blade views. It helps structure complex forms into manageable steps while keeping controller logic clean and configurable.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish the config/views:

composer require smajti1/laravel-wizard
php artisan vendor:publish --provider="Smajti1\Laravel\WizardServiceProvider"

Look first at config/wizard.php — it holds the key knobs you will actually touch: session_key, keep_alive, the classes map (slug → step class), view.layout / view.step / view.finished, and url.prefix / url.middleware. Then skim vendor/smajti1/laravel-wizard/src/Step.php and Wizard.php; that is the whole public API surface, and the README examples mirror it closely.

The canonical first use case is a multi-step form (registration, checkout, onboarding). The minimum viable shape is:

  1. One class per step extending Smajti1\Laravel\Step — set $label, $view, and $rules; override process(Request $request) to capture input and save() to persist.
  2. One wizard class extending Smajti1\Laravel\Wizard with a $steps array of slug → step class. The array order defines the flow.
  3. A controller that instantiates the wizard and delegates GET/POST handling for the current step, plus a finished() action that renders view.finished and forgets session data.
  4. One route pair (GET/POST) with an optional {step?} segment, wired to the prefix/middleware from the config.

If you can render a step, validate it, and land on the finished view, the rest of the package is just variations on that skeleton.

Implementation Patterns

One step, one class, one form. Keep each Step class thin: $rules for validation, process() for extracting request data into the step's $data, save() for the side effect. Resist putting business logic in the controller — the wizard calls save() for you at the right moment, and moving logic into the controller breaks the abstraction (and the back/forward semantics).

Push persistence into save(), not process(). process() runs on submission; save() runs when the step is considered complete. This matters because going back to a step re-renders it with its stored data and does not re-run save(), which is exactly the behaviour you want for "edit previous step without duplicating a record."

Use skip() for conditional branches. Returning true from a step's skip() removes it from the flow without removing it from $steps. Typical use: skip the shipping address when the user picked "pickup", skip a payment step for free plans. The wizard's getNextStep() / getPreviousStep() will jump past skipped steps, so your back-links stay correct for free.

Keep the controller a thin adapter. A single index() method that inspects the current step and returns the step view or redirects to the next slug; a finished() that renders the terminal view and calls forgetAllStepData(). Add the wizard's route group under your own auth middleware — the package's url.middleware is a sensible default but you usually want auth layered on top.

Session is the source of truth between steps. Read prior-step data via the wizard's getStepData() / getAllStepData() helpers when rendering the current step (e.g. showing a summary). Don't stash things in the controller or in hidden inputs — the session is what survives the redirect.

Customise views per step, not globally. Point a step's $view at a specific Blade file when it needs a different layout (review screen, upload step). Leave wizard.layout as the shared chrome so the progress indicator and nav buttons stay consistent.

Gotchas and Tips

save() runs on every forward transition — design for it. If the user goes forward, back, and forward again, save() fires each time. Make it an upsert (updateOrCreate, firstOrCreate) or gate it on $this->wasSaved if you genuinely need once-only behaviour. This is the most common source of duplicate rows in this package.

The session driver bites. Wizard payloads live in the session, so cookie-only sessions silently truncate or fail on anything past a few kilobytes. Use file, database, or redis. If a wizard "works locally, breaks in prod," check SESSION_DRIVER first.

keep_alive is not intuitive. Toggling it changes whether session data is cleared after the finished step. If you see stale wizard data appearing on a second run of the same user, you wanted it false (or you forgot to call forgetAllStepData() on finish).

Slug keys must match across three places. The $steps array keys, the classes map in config/wizard.php, and any {step} URL segment you build must agree. Renaming a key and forgetting the config is a five-minute debugging detour; php artisan config:clear afterwards, always.

Route/prefix drift breaks the back-button. The package generates URLs from url.prefix. If you change it in config but leave your hand-written route('wizard.index') links pointing elsewhere, next/previous links 404. Prefer route names over hardcoded paths in your step views.

Custom validation messages go on the step, not the controller. Override the messages array/method on Step rather than catching ValidationException in the controller — the package re-renders the step with $errors and old input only when validation is routed through it.

Debugging tip. dd(session(config('wizard.session_key'))) after each transition shows you exactly what the wizard thinks it has, which step is current, and which steps were skipped. Faster than stepping through the package source.

Version pinning is real here. 40 stars, last release Dec 2023, and no Laravel 11/12 guarantees in the metadata. Pin smajti1/laravel-wizard in composer.json, run your wizard smoke test on every Laravel minor upgrade, and treat the package as vendored — fork it if you need to patch the Step base class rather than waiting for a release.

Extension points worth knowing. Overriding Wizard::getNextStep() lets you implement branching beyond what skip() covers (e.g. jump from step 2 to step 5 based on a flag). The Step class is the right place to add cross-cutting helpers (file upload normalisation, address formatting) via a shared base class in your app — don't subclass Wizard for that.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource