Currently Available: Need a skilled Software Developer for your next project?
Categories
Laravel

Implementing Team Member Restrictions in Laravel Jetstream + Spark SaaS

Building a SaaS with Laravel Jetstream's team features and Spark for billing. Here's how to restrict team access based on subscription plans.

We'll implement a complete solution to:

  • Restrict team member invitations to specific subscription plans
  • Implement team size limits
  • Show appropriate UI messages to guide users

The Goal

We want to implement the following business rules:

  • Only users with a "Team" plan can add team members
  • Teams are limited to 5 members
  • Users should see clear messages about why they can't add members
  • Users should be guided to upgrade their plan when necessary

1. Configure Spark Plans

First, let's set up our plans in config/spark.php. The important part here is to use the id field to identify plans, which we'll use later for permission checks:

'billables' => [
    'user' => [
        'model' => Team::class,
        'trial_days' => 5,
        'default_interval' => 'monthly',
        'plans' => [
            [
                'id' => 'pro',  // We'll use this ID for checks
                'name' => 'Pro',
                'monthly_id' => env('SPARK_PRO_MONTHLY_PLAN'),
                'yearly_id' => env('SPARK_PRO_YEARLY_PLAN'),
                'features' => [
                    '...",
                ],
            ],
            [
                'id' => 'team', // We'll use this ID for checks
                'name' => 'Team',
                'monthly_id' => env('SPARK_TEAM_MONTHLY_PLAN'),
                'yearly_id' => env('SPARK_TEAM_YEARLY_PLAN'),
                'features' => [
                    '...',
                    'Add team members (max 5)',
                ],
            ],
        ],
    ],
],

2. Implement Team Policy

Create or update your app/Policies/TeamPolicy.php to implement the team member restrictions:

public function addTeamMember(User $user, Team $team): bool
{
    // Must be team owner
    if (!$user->ownsTeam($team)) {
        return false;
    }

    // Must have an active subscription
    $subscription = $team->subscription();
    if (!$subscription) {
        return false;
    }

    // Must be on team plan (not basic or pro)
    $currentPlanId = $subscription->sparkPlan->id;
    if ($currentPlanId !== 'team') {
        return false;
    }

    // Check if team has reached member limit (5)
    if ($team->allUsers()->count() >= 5) {
        return false;
    }

    return true;
}

3. Update the UI

Modify your team management component (resources/js/Pages/Teams/Partials/TeamMemberManager.vue) to show appropriate messages:

<template>
    <FormSection @submitted="addTeamMember">
        <template #title>
            Add Team Member
        </template>

        <template #form>
            <!-- Show message if no subscription or not on team plan -->
            <div v-if="!hasTeamPlan" class="col-span-6">
                <div class="rounded-md bg-yellow-50 p-4">
                    <div class="flex">
                        <div class="flex-shrink-0">
                            <ExclamationTriangleIcon class="h-5 w-5 text-yellow-400" />
                        </div>
                        <div class="ml-3">
                            <h3 class="text-sm font-medium text-yellow-800">
                                Team Plan Required
                            </h3>
                            <div class="mt-2 text-sm text-yellow-700">
                                <p>Team member management is only available with our Team plan.</p>
                                <Link
                                    :href="route('pricing')"
                                    class="font-semibold text-yellow-800 hover:text-yellow-600"
                                >
                                    Upgrade your subscription
                                </Link>
                                to invite team members.
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Show message if team member limit reached -->
            <div v-else-if="reachedMemberLimit" class="col-span-6">
                <div class="rounded-md bg-yellow-50 p-4">
                    <!-- Similar warning for member limit -->
                </div>
            </div>

            <!-- Show add member form only if on team plan and under member limit -->
            <template v-else>
                <!-- Your existing form fields -->
            </template>
        </template>
    </FormSection>
</template>

<script setup>
const hasTeamPlan = computed(() => {
    const subscription = props.team.subscription;
    return subscription && subscription.spark_plan.id === 'team';
});

const reachedMemberLimit = computed(() => {
    return props.team.users.length >= 5;
});
</script>

4. How It Works

  1. Policy Layer: The TeamPolicy enforces the business rules at the backend level, ensuring that even API requests follow the restrictions.

  2. UI Layer: The Vue component provides immediate feedback to users about:

    • Whether they need to upgrade to the Team plan
    • If they've reached the member limit
    • What actions they can take (upgrade, contact support)
  3. Plan Identification: We use the plan's id from the Spark config rather than Paddle plan IDs, making it easier to maintain and modify plans without changing the code.

What I'm building

Delegate tasks. Get software.

Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.

Take a look at vroni.com

Subscribe to my newsletter

Get new posts when I publish them.

I respect your privacy. Unsubscribe at any time.

Leave a Reply

Your email address will not be published. Required fields are marked *