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

Stepped Form Laravel Package

lexal/stepped-form

A Laravel package for building stepped, multi-step forms with ease, letting you split long forms into manageable wizard-style pages while handling validation and state across each step.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require lexal/stepped-form

The package is small (single-digit stars, zero dependents), so treat the source as the documentation. Spend your first ten minutes in this order:

  1. README.md — the canonical usage snippet.
  2. src/ — find the base class you extend (typically something like SteppedForm) and the trait/contract that defines step lifecycle (steps(), render(), handle()).
  3. resources/views/ — if the package ships Blade stubs, publish them (php artisan vendor:publish --provider="Lexal\SteppedForm\..." or check for a tagged publish group).
  4. composer.json — confirm the Laravel/illuminate/* constraint matches your app version; with low adoption you won't get a compatibility patch quickly.

First use case — a 3-step onboarding wizard:

// app/Forms/OnboardingForm.php
namespace App\Forms;

use Lexal\SteppedForm\...\SteppedForm;

class OnboardingForm extends SteppedForm
{
    public function steps(): array
    {
        return [
            'account'  => ['view' => 'forms.steps.account',  'rules' => ['email' => 'required|email']],
            'profile'  => ['view' => 'forms.steps.profile',  'rules' => ['name'  => 'required|string']],
            'confirm'  => ['view' => 'forms.steps.confirm',  'rules' => []],
        ];
    }
}
// routes/web.php
Route::get('/onboarding/{step?}',  [OnboardingController::class, 'show']);
Route::post('/onboarding/{step}',  [OnboardingController::class, 'store']);
public function show(OnboardingForm $form, ?string $step = null)
{
    return view('onboarding', ['step' => $form->render($step)]);
}

Render the step's view inside a shell layout that renders the stepper/progress indicator once, so individual step partials stay dumb.

Implementation Patterns

Controller-as-orchestrator. Keep the form object stateless-ish and let the controller own the request cycle. render($step) returns a view (or view data); handle($request) validates the current step and returns the next step key or a redirect. Your controller becomes a thin switch on that return value.

Session-backed progress. Stepped forms fundamentally need state between requests. Expect the package to stash collected data under a session key (inspect src/ for the exact key format). Pattern:

public function store(Request $request, OnboardingForm $form, string $step)
{
    $result = $form->handle($step, $request->all());

    if ($result->hasErrors()) {
        return back()->withErrors($result->errors())->withInput();
    }

    if ($result->isComplete()) {
        $data = $result->data();
        Onboarding::create($data);          // persist
        $form->forget();                    // clear session state
        return redirect()->route('dashboard');
    }

    return redirect()->route('onboarding', $result->nextStep());
}

Per-step validation, not whole-form validation. The value of a stepped form is validating one slice at a time. Define rules() keyed by step and make sure your handle() call only pulls the current step's rules — never $request->validate($allRules).

Back navigation. Implement "previous" as a separate route or a hidden _direction=back input that rewinds the pointer without validating or re-saving. If the package's handle() always validates, add an explicit previous()/step(-1) call rather than posting an empty payload.

Branching steps. For conditional flows (e.g. skip "company details" for individuals) override the step-resolution method — usually nextStep() — and return a different key based on already-collected data. Keep branching logic in one place; scattering it across controllers is the fastest way to create unreachable steps.

Review step. Treat the summary as just another step whose view renders $form->data() read-only. This is where session state earns its keep and where you'll catch half the integration bugs.

Testing. Feature-test the wizard as a sequence of POSTs, asserting session state and the final DB write:

$this->post('/onboarding/account', ['email' => '[email protected]'])->assertRedirect('/onboarding/profile');
$this->post('/onboarding/profile', ['name' => 'Ada'])->assertRedirect('/onboarding/confirm');

Livewire pairing. If you're on Livewire, don't mix paradigms — either wrap the whole wizard in one Livewire component and call render()/handle() from actions, or stay fully controller-driven. Session state and Livewire's component state will fight if you split them.

Gotchas and Tips

  • Two stars means you are the support channel. Pin the exact version in composer.json ("lexal/stepped-form": "1.2.*" rather than ^1.2) and diff the source on upgrade. The last release date is recent, which suggests active churn — read the changelog before bumping.
  • Session key collisions. If a user opens two tabs with two different wizard instances, or you nest a second stepped form in the same session, keys can clash. Namespace per form instance (pass a unique key/identifier into the form) if the package supports it; otherwise flush between runs and test the two-tab case.
  • Validation scope leaks. The most common bug: step 2 validation runs rules for step 3 because rules were merged. Assert with a test that posting step 2 with step-3 data missing still passes.
  • Refresh/resubmit. After a successful step POST you redirect — always. If you render the next step directly from the POST response, browser refresh duplicates the submission.
  • Back button after a browser refresh. Session pointers survive; flash data doesn't. Don't rely on old() for anything except the immediately preceding step's validation failure.
  • File uploads across steps. Uploads can't sit in the session blob. Store them to disk at the step where they're uploaded and put only the path in the form data; validate existence at the final step.
  • View resolution. If step views are package-shipped, they may resolve from the package namespace and ignore your app's resources/views. Publish them, then override; check view() calls in src/ for the exact namespace.
  • Extension points to prefer: overriding steps(), rules(), nextStep(), and the step view path. Avoid touching vendor code — the fork will rot immediately given the release cadence.
  • Debugging: dd(session()->all()) after each step to see exactly what the package is persisting; the key naming will tell you whether state is namespaced. Add a temporary Log::info() in the base class's handle() if step transitions misbehave.
  • Escape hatch. If the package's step model doesn't fit (dynamic step counts, DB-driven steps), it's ~200 lines of logic you can reimplement with a session() array and per-step rule sets in an afternoon. Budget for that rather than fighting the API.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package