spatie/laravel-permission · warning

Role `{$role->name}` already exists on the global team; argu

Error message

Role `{$role->name}` already exists on the global team; argument --team-id has no effect

What it means

With teams enabled, permission:create-role --team-id=X looks the role up via Role::findOrCreate(). The underlying query matches roles where the teams column IS NULL (global role) OR equals the current team id, so an existing global role with the same name and guard is found and returned instead of a team-scoped one being created. When the returned role's team key is null, the command warns that the --team-id you passed had no effect, and proceeds to attach the listed permissions to that pre-existing global role.

Source

Thrown at src/Commands/CreateRoleCommand.php:39

    public function handle(PermissionRegistrar $permissionRegistrar): int
    {
        $roleClass = app(RoleContract::class);

        $teamIdAux = getPermissionsTeamId();
        setPermissionsTeamId($this->option('team-id') ?: null);

        if (! $permissionRegistrar->teams && $this->option('team-id')) {
            $this->warn('Teams feature disabled, argument --team-id has no effect. Either enable it in permissions config file or remove --team-id parameter');

            return self::SUCCESS;
        }

        $role = $roleClass::findOrCreate($this->argument('name'), $this->argument('guard'));
        setPermissionsTeamId($teamIdAux);

        $teams_key = $permissionRegistrar->teamsKey;
        if ($permissionRegistrar->teams && $this->option('team-id') && is_null($role->$teams_key)) {
            $this->warn("Role `{$role->name}` already exists on the global team; argument --team-id has no effect");
        }

        $role->givePermissionTo($this->makePermissions($this->argument('permissions')));

        $this->info("Role `{$role->name}` ".($role->wasRecentlyCreated ? 'created' : 'updated'));

        return self::SUCCESS;
    }

    protected function makePermissions(?string $string = null): ?Collection
    {
        if (empty($string)) {
            return null;
        }

        $permissionClass = app(PermissionContract::class);

        $permissions = explode('|', $string);

View on GitHub (pinned to afd24018f6)

Solutions

  1. Delete or rename the existing global role first (e.g. in tinker: Spatie\Permission\Models\Role::where('name','writer')->whereNull('team_id')->first()?->delete()), then re-run the command with --team-id
  2. Create the team-scoped role explicitly with the team id set: in tinker, setPermissionsTeamId(2); Role::findOrCreate('writer', 'web') — or Role::create(['name' => 'writer', 'guard_name' => 'web', 'team_id' => 2])
  3. If a global role with that name is actually acceptable, drop --team-id and let the command update the global role deliberately
  4. Adopt per-team role naming (e.g. 'writer-team-2') if your app logic cannot tolerate the global-first lookup

Example fix

# before — a global 'writer' role exists (team_id NULL)
php artisan permission:create-role writer web "edit posts" --team-id=2
# warning: Role `writer` already exists on the global team; argument --team-id has no effect

# after — remove the global role, then create the team-scoped one
php artisan tinker --execute="\Spatie\Permission\Models\Role::where('name','writer')->whereNull('team_id')->first()?->delete();"
php artisan permission:create-role writer web "edit posts" --team-id=2
Defensive patterns

Strategy: validation

Validate before calling

// before creating a team-scoped role, make sure no global role shadows it
$registrar = app(\Spatie\Permission\PermissionRegistrar::class);
$globalExists = \Spatie\Permission\Models\Role::where('name', 'writer')
    ->where('guard_name', 'web')
    ->whereNull($registrar->teamsKey)
    ->exists();
if ($globalExists) {
    // rename/delete the global role, or pick a per-team name, before running with --team-id
}

Prevention

When it happens

Trigger: Running `php artisan permission:create-role writer web "edit posts" --team-id=2` when a role named 'writer' for guard 'web' already exists with team_id NULL. findOrCreate() returns the global role, the `$permissionRegistrar->teams && $this->option('team-id') && is_null($role->$teams_key)` condition is true, and the warning fires.

Common situations: Migrating a single-tenant app to teams: all pre-existing roles are global (team_id NULL), so the first team-scoped create for an existing name updates the global role instead; two teams that should each have their own 'admin' role collide with the global 'admin'; seeders that create global roles first and later run team-scoped create commands.

Related errors


AI-assisted analysis of spatie/laravel-permission@afd24018f6 (2026-08-21). Data as JSON: /api/errors/beebb46090c16141. Report an issue: GitHub.