Looking to hire Laravel developers? Try LaraJobs

laravel-team maintained by labrodev

Description
Teams, memberships, invitations, roles and plan/billing mechanics for Laravel SaaS applications — the Labrodev multi-tenancy core.
Author
Last update
2026/07/22 12:51 (dev-main)
License
Downloads
0

Comments
comments powered by Disqus

labrodev/laravel-team

Teams, memberships, invitations, roles and plan/billing mechanics for Laravel SaaS applications — the Labrodev multi-tenancy core.

The package owns the mechanism: teams with owner/admin/member roles, invitation flow, a per-team subscription-plan caps snapshot (-1 = unlimited), plan checkout / swap / cancel orchestration behind a gateway contract, and a team morph alias so polymorphic rows (e.g. a billing provider's billable_type) never depend on class FQCNs.

The application owns the policy: its plan enum (cases, pricing, cap keys), the rules that consume caps, and all UI. Payment providers are adapter packages implementing the BillingClient contract — see labrodev/laravel-team-paddle.

Installation

composer require labrodev/laravel-team

php artisan vendor:publish --tag=team-config
php artisan vendor:publish --tag=team-migrations
php artisan migrate

Wiring the application

1. User model

use Labrodev\Team\Concerns\HasTeams;
use Labrodev\Team\Contracts\TeamMember;

final class User extends Authenticatable implements TeamMember
{
    use HasTeams;
}

The users table needs a nullable current_team_id column.

2. Plan enum

Define a string-backed enum implementing Labrodev\Team\Contracts\PlanContract and point config/team.php at it:

enum Plan: string implements PlanContract
{
    case Free = 'free';
    case Pro = 'pro';

    public function label(): string { /* ... */ }
    public function isPaid(): bool { /* ... */ }

    /** @return array<string, mixed> */
    public function caps(): array
    {
        return match ($this) {
            self::Free => ['monitors' => 3, 'seats' => 1],
            self::Pro => ['monitors' => -1, 'seats' => -1],
        };
    }
}
// config/team.php
'user_model' => App\Models\User::class,
'plan_enum' => App\Enums\Plan::class,
'free_plan' => 'free',

TeamCreate snapshots the free plan's caps() onto the team's subscription_plans row, so later plan-definition changes never retro-affect existing teams. Read caps via $team->subscriptionPlan->cap('monitors').

Applications that prefer typed cap columns can add them in their own migration on top of the published subscription_plans table and bind their own snapshot logic.

3. Exception rendering

Package exceptions never construct a ValidationException. They implement one of two contracts; register render mappings once (e.g. bootstrap/app.php):

use Labrodev\Team\Contracts\ProvidesFieldError;
use Labrodev\Team\Contracts\ProvidesFlashError;

$exceptions->render(fn (ProvidesFieldError $e, Request $r) => $r->expectsJson()
    ? response()->json(['message' => $e->userMessage(), 'errors' => [$e->field() => [$e->userMessage()]]], 422)
    : redirect()->back()->withErrors([$e->field() => $e->userMessage()]));

$exceptions->render(fn (ProvidesFlashError $e, Request $r) => $r->expectsJson()
    ? response()->json(['message' => $e->userMessage()], 422)
    : redirect()->back()); // + your flash-toast mechanism

4. Billing gateway

Bind an adapter implementing Labrodev\Team\Contracts\BillingClient (or install labrodev/laravel-team-paddle). With team.sandbox enabled, StartPlanCheckout bypasses the gateway and applies the target plan directly — for local development without a configured payment provider.

Usage

// Create a team (owner membership + plan snapshot + current-team switch, in one transaction)
$team = app(TeamCreate::class)(user: $user, teamCreateData: new TeamCreateData(name: 'Acme'));

// Invite / accept
app(TeamInvitationCreate::class)(team: $team, user: $inviter, teamInvitationCreateData: $data);
app(TeamInvitationAcceptedStatusUpdate::class)(teamInvitation: $invitation, user: $user);

// Plans
app(StartPlanCheckout::class)(team: $team, plan: Plan::Pro, customerName: ..., customerEmail: ..., returnUrl: ...);

Authorization: TeamPolicy ships with view/update/delete/member-management/manageBilling abilities driven by TeamRole/TeamPermission.

Testing

composer check   # pint --test, phpstan (level 7), rector --dry-run, pest

License

MIT — see LICENSE.md.