NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Onboarding for Laravel

Without the facade

The facade is the recommended default, not a requirement. The facade root, RoundlyConsulting\Onboarding\OnboardingManager, is a container singleton — inject it through the constructor for exactly the same API, as an explicit dependency with no static calls:

use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use RoundlyConsulting\Onboarding\OnboardingManager;

final class OnboardingController
{
    public function __construct(private OnboardingManager $onboarding) {}

    public function show(Request $request): JsonResponse
    {
        return response()->json($this->onboarding->for($request->user())?->toData());
    }

    public function dismiss(Request $request, string $step): RedirectResponse
    {
        $this->onboarding->for($request->user())?->dismiss($step);

        return back();
    }
}

Onboarding::fake() swaps the container binding as well as the facade, so an injected OnboardingManager gets the fake too.

No action classes

Unlike most Roundly packages, Onboarding ships no actions to compose: flows and steps are declarative, and the manager registers, resolves and persists them. The facade and the injected manager are the two entry points.

Manager methods

MethodReturnsPurpose
register($key, $flow = null) / flow(string $key)OnboardingManager / FlowRegister a flow (steps array, Flow or class) / start one fluently.
for($subject, ?string $key = null)?FlowA copy of the subject’s flow, bound to it.
find(string $key, ?Flow $default = null)?FlowThe registered definition.
has() / all() / forget() / flush()—Inspect and edit the registry.
resolveUsing(Closure) / resolveFor($subject, ?string $fallback = null)OnboardingManager / ?FlowPick the flow per subject.
useStore(OnboardingStore|string $store)OnboardingManagerConfigure persistence.
store() / hasStore()?OnboardingStore / boolThe store in use.

dismissStep(Flow $flow, string $step) is public but @internal — the single path every dismissal takes, so the fake sees them all. Dismiss through Flow::dismiss() or $user->dismissOnboardingStep() instead.

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 crypto

By 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.