Optional steps
Mark a step optional() so it guides the user without blocking completion. Optional steps are ignored by isCompleted() and requiredPercentageCompleted(), but still count toward the default percentageCompleted():
$flow = Onboarding::flow('default')->title('Profile Onboarding');
$flow->add('Upload photo')->key('photo')->completeWhenFilled('avatar_path');
$flow->add('Add a bio')->key('bio')->optional()
->completeIf(fn (?User $user) => filled($user?->bio));// $user has a photo but no bio
$flow = Onboarding::for($user);
$flow?->isCompleted(); // true — optional steps never block
$flow?->percentageCompleted(); // 50.0 — optional steps still count
$flow?->requiredPercentageCompleted(); // 100.0 — required steps only
$flow?->currentStep()?->stepKey(); // 'bio' — still worth suggesting
$flow?->requiredSteps(); // Collection<int, Step>
$flow?->optionalSteps(); // Collection<int, Step>Switching back
Step::make('Add a bio')->optional(); // optional(true)
Step::make('Add a bio')->optional(false); // back to required
Step::make('Add a bio')->required(); // the same as optional(false)Finished versus next
currentStep() walks every visible step, so once all required steps are done it can still return an incomplete optional step. Use isCompleted() to decide whether onboarding is finished, and currentStep() to decide what to suggest next.
Dismissible optional steps
With a persistence store configured, an optional step marked dismissible() can be dismissed by the user and drops out of the list and the percentages — see Persistence store.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.