php 69 lines · 9 steps

Building a multi-step onboarding wizard in Laravel

A Laravel controller drives a session-backed wizard, validating each step and assembling the pieces into one final record.

Explained by highlit
1<?php
2 
3namespace App\Http\Controllers;
4 
5use Illuminate\Http\Request;
6use Illuminate\Support\Facades\Validator;
7use App\Models\Registration;
8 
9class OnboardingWizardController extends Controller
10{
11 private const SESSION_KEY = 'onboarding.wizard';
12 
13 private array $steps = [
14 'account' => ['name' => 'required|string|max:120', 'email' => 'required|email'],
15 'company' => ['company' => 'required|string|max:200', 'size' => 'required|in:solo,small,large'],
16 'billing' => ['plan' => 'required|in:free,pro,enterprise'],
17 ];
18 
19 public function show(Request $request, string $step)
20 {
21 abort_unless(array_key_exists($step, $this->steps), 404);
22 
23 $state = $request->session()->get(self::SESSION_KEY, []);
24 
25 return view("onboarding.{$step}", [
26 'step' => $step,
27 'data' => $state[$step] ?? [],
28 'progress' => $this->progress($step),
29 ]);
30 }
31 
32 public function store(Request $request, string $step)
33 {
34 abort_unless(array_key_exists($step, $this->steps), 404);
35 
36 $validated = Validator::make($request->all(), $this->steps[$step])->validate();
37 
38 $request->session()->put(self::SESSION_KEY . ".{$step}", $validated);
39 
40 $keys = array_keys($this->steps);
41 $next = $keys[array_search($step, $keys) + 1] ?? null;
42 
43 if ($next === null) {
44 return $this->finish($request);
45 }
46 
47 return redirect()->route('onboarding.show', ['step' => $next]);
48 }
49 
50 private function finish(Request $request)
51 {
52 $state = $request->session()->get(self::SESSION_KEY, []);
53 
54 $registration = Registration::create(array_merge(...array_values($state)));
55 
56 $request->session()->forget(self::SESSION_KEY);
57 
58 return redirect()
59 ->route('dashboard')
60 ->with('status', "Welcome aboard, {$registration->name}!");
61 }
62 
63 private function progress(string $current): int
64 {
65 $keys = array_keys($this->steps);
66 
67 return (int) round((array_search($current, $keys) + 1) / count($keys) * 100);
68 }
69}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Storing partial form state in the session lets a wizard survive across separate HTTP requests.
  2. 2Defining each step's rules in one data structure keeps validation, ordering, and progress in sync.
  3. 3Merging the accumulated per-step state into a single create call defers persistence until the flow completes.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a multi-step onboarding wizard in Laravel — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code