---
title: "Registering flows — Onboarding for Laravel | Roundly"
description: "Register default and keyed flows, build them fluently, package reusable flow classes and manage the registry."
url: https://roundly-consulting.com/open-source/docs/onboarding-for-laravel/registering-flows
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Onboarding for Laravel](https://roundly-consulting.com/open-source/docs/onboarding-for-laravel.md)

# Registering flows

Register flows once — typically in the boot() method of one of your application’s service providers:

```php
namespace App\Providers;

use App\Models\User;
use Illuminate\Support\ServiceProvider;
use RoundlyConsulting\Onboarding\Facades\Onboarding;
use RoundlyConsulting\Onboarding\Flow;
use RoundlyConsulting\Onboarding\Step;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Onboarding::register(Flow::make('Profile Onboarding')->of([
            Step::make('Upload Photo')
                ->cta('Upload Now')
                ->action('users@photo@upload') // a route name, URL, or any hint your frontend understands
                ->completeIf(fn (?User $user) => (bool) $user?->photo),
        ]));
    }
}
```

Registering without a key stores the flow under the default key, default (held in OnboardingManager::$default). Register additional flows under explicit keys:

```php
Onboarding::register('admin', Flow::make('Admin Setup')->of([
    Step::make('Invite your team'),
    Step::make('Connect billing')->completeIf(fn (?User $user) => $user?->hasBilling()),
]));
```

You may also register a plain array of steps — it is wrapped in a Flow for you, and with no key it becomes the default flow:

```php
Onboarding::register([
    Step::make('Upload Photo')->cta('Upload Now'),
]);
```

## Fluent registration

Onboarding::flow() creates, registers and returns an empty flow under the key — replacing any flow already registered there. add() appends a step and returns that Step, so step builders chain straight off it:

```php
Onboarding::flow('default')
    ->title('Profile Onboarding')
    ->add('Upload Photo')->key('upload-photo')->cta('Upload Now')
        ->action('users@photo@upload')
        ->completeIf(fn (?User $user) => (bool) $user?->photo);
```

Because add() returns the Step rather than the flow, start each further step from a flow variable — or append ready-made steps with addStep():

```php
$flow = Onboarding::flow('default')->title('Profile Onboarding');

// add() returns the new Step, so start every step from the flow
$flow->add('Upload Photo')->key('upload-photo')->completeWhenFilled('avatar_path');
$flow->add('Verify email')->key('verify-email')->completeWhenTrue('hasVerifiedEmail');

// or append ready-made steps (variadic, returns the Flow)
$flow->addStep(
    Step::make('Invite team')->completeWhenHas('teams'),
    Step::make('Add a bio')->optional()->completeWhenFilled('bio'),
);
```

## Custom flow classes

For reusable flows, extend Flow and configure it in the protected setup() hook, which runs from the constructor:

```php
namespace App\Onboarding;

use RoundlyConsulting\Onboarding\Flow;
use RoundlyConsulting\Onboarding\Step;

final class UserOnboarding extends Flow
{
    protected function setup(): void
    {
        $this->title('User Onboarding')->of([
            Step::make('Verify email')->key('verify-email')->completeWhenTrue('hasVerifiedEmail'),
            Step::make('Upload photo')->key('photo')->route('profile.photo')->completeWhenFilled('avatar_path'),
            Step::make('Add a bio')->key('bio')->optional()->completeWhenFilled('bio'),
        ]);
    }
}
```

Register a custom flow by its class string — the registry instantiates it:

```php
use App\Onboarding\UserOnboarding;
use RoundlyConsulting\Onboarding\Facades\Onboarding;

Onboarding::register(UserOnboarding::class);            // stored as the default flow
Onboarding::register('user', UserOnboarding::class);    // stored under the "user" key
```

## Managing the registry

```php
Onboarding::find('admin');                 // ?Flow — the registered definition
Onboarding::find('missing', new Flow());   // the fallback when the key is unknown
Onboarding::has('admin');                  // bool
Onboarding::all();                         // Collection<string, Flow>, keyed by flow key
Onboarding::forget('admin');               // remove one flow
Onboarding::flush();                       // remove all flows and the resolver
```

find() returns the registered definition itself — use it to extend a flow at boot, and read through Onboarding::for() so no subject is bound to the shared instance. Every manager method is listed in The Onboarding facade.

Registering a class string that is not Flow or a subclass throws InvalidFlowException — see Extending & exceptions.

## 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](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

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

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/onboarding-for-laravel.md)
