---
title: "Roles — Teams for Laravel | Roundly"
description: "Register roles with permissions in code or the database, use the * wildcard, and cache the database driver."
url: https://roundly-consulting.com/open-source/docs/teams-for-laravel/roles
language: en
---

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

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

# Roles

Roles are named bundles of permissions. A membership stores only the role key; the role itself resolves through a role provider, which Teams::roles() returns. Register roles in a service provider’s boot() method — a key, a display name, optional permissions (strings or Permission objects) and an optional description. The \* permission grants everything:

```php
use RoundlyConsulting\Teams\Facades\Teams;
use RoundlyConsulting\Teams\Roles\Permission;

// In a service provider's boot() method:
Teams::roles()->register('owner', 'Owner', ['*']);
Teams::roles()->register('admin', 'Admin', ['*']);
Teams::roles()->register(
    'editor',
    'Editor',
    ['posts.edit', new Permission('posts.publish', 'Publish posts')],
    description: 'Can edit and publish content.',
);
Teams::roles()->register('member', 'Member', ['posts.view']);

Teams::roles()->all();           // array<string, Role>
Teams::roles()->find('editor');  // ?Role
```

Register every key your app assigns — including owner, admin and member, which the package itself assigns on team creation, ownership transfer and join-request approval (configurable under roles.\*). A membership whose key isn’t registered resolves no role and has no permissions.

## The Role object

```php
$role = Teams::roles()->find('editor');

$role->key;                 // 'editor'
$role->name;                // 'Editor'
$role->description;         // 'Can edit and publish content.'
$role->permissions;         // ['posts.edit', 'posts.publish']
$role->permissionObjects;   // list<Permission>

$role->hasPermission('posts.publish');                      // '*' grants everything
$role->hasAnyPermission(['posts.delete', 'posts.edit']);    // true
$role->hasAllPermissions(['posts.edit', 'posts.publish']);  // true
```

An empty permission name never passes, even with \*.

## Role drivers

| roles.provider | Class | Behaviour |
| --- | --- | --- |
| `array` | InMemoryRoleProvider | Default. Roles registered at boot live in memory for the whole process — fast and version-controlled. |
| `database` | DatabaseRoleProvider | Persists roles as team\_roles rows (upsert by key); the role map is read at most once per request or queued job. |
| `database + cache` | CachedRoleProvider | Wraps the database driver; caches the role map and flushes it whenever a role definition changes. |

## Storing roles in the database

Set roles.provider to database and the same Teams::roles()->register() call upserts a team\_roles row by key, so admins can manage roles at runtime. Permission checks don’t change:

```php
// .env → TEAMS_ROLES_PROVIDER=database
Teams::roles()->register('editor', 'Editor', ['posts.edit', 'posts.publish'], description: 'Writes posts'); // upserts a team_roles row by key
```

register() is an upsert on the key with either driver: registering a key again replaces its name, permissions and description, and a soft-deleted definition is restored rather than re-inserted. The Role it returns is a read-only result — change a role by registering it again.

## Caching the database driver

Set roles.cache.enabled to cache the database role map. It’s flushed on every Teams::roles()->register() — tag-aware stores by tag, other stores by forgetting the single key. The cache wraps only the database driver:

```bash
TEAMS_ROLES_PROVIDER=database
TEAMS_ROLES_CACHE=true
TEAMS_ROLES_CACHE_STORE=redis     # null = the default store
TEAMS_ROLES_CACHE_KEY=teams.roles
TEAMS_ROLES_CACHE_TTL=3600        # seconds
```

## Revocations in long-lived processes

The role provider is bound scoped: the role map is read at most once per request or queued job — queue workers and Octane start each one with a fresh provider — so a permission revoked by another process stops granting from the next request or job. Saving or deleting a role definition, through register() or straight through the RoleDefinition model from an admin screen, flushes the current process’s map and the shared cache at once, and a cache refill always reads the table, never a worker’s older copy.

## Listing roles

```bash
php artisan teams:roles         # Key | Name | Permissions
```

## 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/teams-for-laravel.md)
