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

Declarative conditions

For the common cases you don’t need a closure. These helpers read a value off the bound subject and compile down to the same completeIf / excludeIf rules, so they tolerate a null subject and stay fully stateless:

Step::make('Upload photo')->completeWhenFilled('avatar_path');     // filled() on the attribute (dot paths supported)
Step::make('Verify email')->completeWhenTrue('email_verified_at'); // truthy attribute, or a no-arg method
Step::make('Invite team')->completeWhenHas('teams');               // a non-empty relation value, collection, or array
MethodPurpose
completeWhenFilled($attribute)filled() on the attribute; dot paths supported.
completeWhenTrue($attributeOrMethod)A no-argument method’s result, or else the attribute, is truthy.
completeWhenHas($relation)The relation value is a model, a non-empty collection or array, or any non-null value.
excludeWhenFilled($attribute)The completeWhenFilled() check, used for exclusion.
excludeWhenTrue($attributeOrMethod)The completeWhenTrue() check, used for exclusion.
excludeWhenHas($relation)The completeWhenHas() check, used for exclusion.

With a null subject every helper evaluates to false — the step is not complete and not excluded.

Methods before attributes

completeWhenTrue() prefers a no-argument method of that name over an attribute. A method that requires arguments is treated as no value rather than being called:

// A no-arg method wins over an attribute of the same name
Step::make('Subscribe')->completeWhenTrue('hasSubscription');   // calls $subject->hasSubscription()

Relations never query

completeWhenHas() inspects the already-loaded or lazy-loaded relation value — it does not run ->exists() for you, which would assume Eloquent. When you need an explicit query, back completeWhenTrue() with a model method, or use a closure:

use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Onboarding\Traits\GetsOnboarded;

class User extends Model
{
    use GetsOnboarded;

    public function hasTeam(): bool
    {
        return $this->teams()->exists();
    }
}

// completeWhenHas() never queries — back an explicit query with a method
Step::make('Invite team')->completeWhenTrue('hasTeam');

Exclusion

The same set exists on the exclude side. An excluded step disappears from steps(), from the percentages and from completion for that subject — each person only sees what applies to them:

Step::make('Connect billing')->excludeWhenTrue('is_free_plan');     // hidden for free-plan users
Step::make('Upload photo')->excludeWhenFilled('social_avatar_url'); // already has an avatar elsewhere
Step::make('Create workspace')->excludeWhenHas('workspaces');       // joined an existing workspace

completeWhen() and excludeWhen() are aliases of completeIf() and excludeIf() for a consistent naming style:

Step::make('Verify email')->completeWhen(fn (?User $user) => $user?->hasVerifiedEmail());
Step::make('Connect billing')->excludeWhen(fn (?User $user) => $user?->is_guest);

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.