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

Http Stepped Form Laravel Package

lexal/http-stepped-form

A Laravel package for building HTTP-driven multi-step forms, where each step is handled by its own request while preserving state, validation, and navigation across the flow. Ideal for wizard-style forms that need server-side step management.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a single-maintainer, low-adoption package (1 star, 0 dependents, no downstream users to learn from), so treat the source as the documentation. Budget 30–45 minutes to read src/ and the tests before wiring it into anything.

Install and inspect

composer require lexal/http-stepped-form
# The three things that actually tell you how it works:
ls vendor/lexal/http-stepped-form/src
cat vendor/lexal/http-stepped-form/README.md
ls vendor/lexal/http-stepped-form/tests   # if present, this is the real spec

Check composer.json in the package for require (PHP version, PSR-7/PSR-15 vs. framework-coupled) and autoload. There is no Laravel service provider advertised in the metadata — if there isn't one, you register it yourself in config/app.php or bind it in a FormServiceProvider, and publish config with php artisan vendor:publish --tag=... only if a config file exists.

Where to look first

  1. The step/state container class — how a form's state survives between requests (session key, hidden token, or signed payload). This single decision drives every integration choice below.
  2. The validation hook — whether per-step validation is delegated to you (Laravel validator) or baked in.
  3. The transition method — what it returns (redirect URL? view? a result object?) so you know how much of the HTTP layer you own.

First use case

A server-rendered multi-step wizard — onboarding, checkout details, multi-page application form — where each step is a real GET/POST round trip and no JavaScript state store is needed. That is exactly the niche the package name implies, and it's where it will earn its keep versus a hand-rolled session()->put('wizard.'.$step, $data).

Start with a 3-step form in a single controller and one Blade view per step; do not introduce Livewire or an SPA at the same time, or you lose the ability to tell which layer broke.

Implementation Patterns

Route shape. One route pair for all steps beats N route pairs — the step is a parameter, so adding a step is a config change, not a routing change:

Route::middleware(['web'])->group(function () {
    Route::get('/apply/{step?}',  [ApplicationController::class, 'show'])->name('apply.show');
    Route::post('/apply/{step}',  [ApplicationController::class, 'store'])->name('apply.store');
});

Controller as a thin adapter. Keep the package in the controller and out of your Blade and models. The controller's job is: resolve the step, hand the request input to the form, translate the result into a redirect or a view with errors.

public function store(Request $request, string $step)
{
    $form = $this->formFactory->forUser($request->user()); // your wrapper

    if (! $form->accepts($step)) {
        return redirect()->route('apply.show', ['step' => $form->currentStep()]);
    }

    $result = $form->submit($step, $request->all()); // illustrative — confirm the real signature

    return $result->isComplete()
        ? redirect()->route('apply.review')
        : redirect()->route('apply.show', ['step' => $result->nextStep()]);
}

Always redirect after POST (PRG). Stepped forms that render a view directly on POST break refresh, back-button, and double-submit in ways users will find and report.

Wrap the package. With 1 star, you want a seam. Put a SteppedForm/FormDefinition wrapper in app/Forms that owns the package instance, your step list, and your validation rules. If the package is abandoned or its state model doesn't fit, you swap one class. Do not let lexal\... types leak into controllers, jobs, or views.

State storage. Two viable models, and the package will favour one:

  • Session-backed — simplest, plays well with Laravel's web middleware, but dies with the session and is awkward for email-resume links.
  • Token/payload-backed — state travels in a signed hidden field or a DB row keyed by an opaque ID. Survives session expiry, enables "resume later", but every field goes through the client, so re-validate on the final step regardless of earlier validation.

If the package persists to session, key it per-user and per-form-instance (e.g. apply.{$draftId}), otherwise two tabs of the same wizard stomp each other.

Validation per step, re-validated at the end. Define rules in a rulesFor(string $step): array method on your wrapper, feed them to Laravel's Validator, and on final submit run the full rule set once more. Never trust that step 1 was validated because step 3 arrived.

Back navigation. Make back a plain GET to a lower step number and explicitly allow it. If your transition logic refuses backward moves, the browser back button still works and will desync the "current step" pointer — so either support backward transitions or make the step pointer derived from the data (highest step with complete data) rather than stored separately.

Testing. Feature-test the whole flow as a sequence of $this->post(...) calls asserting redirect targets and final persisted model — not the internal step object. One test that walks 0→N and one that attempts to jump 0→N directly.

Gotchas and Tips

Maturity risk is the headline. One star, zero dependents, last release 2026-08-11. There is no community to answer questions and no guarantee of a Laravel 12/13 compatibility pass. Pin the version in composer.json, and write a test that covers your happy path so a composer update breaking it is caught by you, not by production.

Check for framework coupling before you commit. If the package returns PSR-7 responses or expects PSR-15 middleware, you'll be adapting it to Laravel's Request/RedirectResponse yourself. That's a small but real cost — decide it's worth it versus ~150 lines of your own session-backed wizard.

Session locking and concurrency. Laravel's file/database session driver locks per session, so two rapid POSTs from the same browser serialise — usually good, occasionally a surprise under load. Double-submits from a slow connection are the classic stepped-form bug: use ->middleware('throttle:...') on the POST route, disable the submit button client-side, and make the final-submit handler idempotent (unique constraint or firstOrCreate) because the user will get the double-created order.

Session expiry mid-form. Sessions die after config('session.lifetime'). A user who leaves the tab open over lunch loses everything and gets a confusing redirect to step 1. Handle it explicitly: if the state container reports "unknown form instance", redirect to the first step with a flash message rather than 404 or 500.

File uploads. Stepped forms and uploads interact badly when the payload round-trips through the client or through session storage. Store uploads immediately to a temp disk keyed by the draft ID and keep only the path in form state; clean up orphaned temp files with a scheduled command.

Don't store sensitive fields in client-visible state. If the state model signs a payload rather than storing server-side, ensure it's encrypted (Laravel's Crypt) not merely HMAC'd, and keep passwords/PCI data out of it entirely.

Extension points to probe. Look for interfaces or abstract classes for step resolution and state persistence — those are your override points. Subclassing a concrete class to change one behaviour is the signal to write your own wrapper instead and keep the package at arm's length.

Debugging. Because the state lives outside the request, dump the resolved state container at the top of store() (dd($form->state())) when a step keeps redirecting to the wrong place. Nine times out of ten it's a stale instance key or a session that was regenerated by a login mid-flow — note that Auth::login() calls session()->regenerate(), which can orphan session-backed form state if the package caches the old ID.

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib