---
title: "Permissions — Connections for Laravel | Roundly"
description: "Grant, revoke, sync and clear per-connection permissions; check one, any or all of them, with * and segment wildcards."
url: https://roundly-consulting.com/open-source/docs/connections-for-laravel/permissions
language: en
---

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

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

# Permissions

Every connection carries its own set of permission strings — whatever names your app uses, such as view, edit or publish. The same two models can be connected with different privileges in different contexts. Sets are de-duplicated automatically.

## Changing permissions

```php
use RoundlyConsulting\Connections\Facades\Connections;

$permissions = Connections::between($user, $team)->permissions();

$permissions->grant('publish');          // Connection — creates the connection if absent
$permissions->revoke('publish');         // Connection — throws ConnectionNotFound if absent
$permissions->sync('view', 'edit');      // Connection — exact set, creates if absent
$permissions->clear();                   // Connection — remove every permission; throws ConnectionNotFound if absent

$permissions->all();                     // Collection<int, string> — what is stored
$permissions->has('edit');               // bool
$permissions->hasAny('view', 'edit');    // bool
$permissions->hasAll('view', 'edit');    // bool
```

- grant() adds to the set and creates the connection when none exists.
- revoke() removes from the set and throws ConnectionNotFound when there is no connection.
- sync() replaces the set with exactly the given permissions and creates the connection when none exists.
- clear() empties the set and, unlike sync(), never creates a connection — it throws ConnectionNotFound when there is none.
- Pass the permissions explicitly — grant() and sync() don’t fall back to permissions staged with withPermissions().
- ConnectionPermissionsChanged fires only when the set actually changes, carrying the previous and current lists.
- Each write is one transaction that locks the pair’s row, so concurrent writes to one pair run one after another: a revoke can’t be undone by a grant running at the same time, and a grant that loses the race to create the connection adds to the winner’s permissions.

The trait exposes the same edits as model methods — shorthand that runs through the same manager:

```php
$user->grantThroughConnection($team, 'publish', 'archive');
$user->revokeThroughConnection($team, 'archive');
$user->syncConnectionPermissions($team, ['view', 'edit']);
$user->clearConnectionPermissions($team);

$user->permissionsThroughConnection($team);   // Collection<int, string> of the stored set
```

## Checking permissions

```php
use RoundlyConsulting\Connections\Facades\Connections;

Connections::between($user, $team)->permissions()->has('edit');              // bool
Connections::between($user, $team)->permissions()->hasAny('view', 'edit');   // bool
Connections::between($user, $team)->permissions()->hasAll('view', 'edit');   // bool

// Trait shorthand — the same checks from the connector's side.
$user->hasPermissionThroughConnection($team, 'edit');             // bool
$user->hasPermissionThroughConnection($team, 'edit', force: true);// bypass the cache
$user->hasAnyPermissionThroughConnection($team, 'view', 'edit');  // bool
$user->hasAllPermissionsThroughConnection($team, 'view', 'edit'); // bool

// On a Connection row you already hold.
$connection->hasPermission('edit');             // bool
$connection->hasAnyPermission('view', 'edit');  // bool
$connection->hasAllPermissions('view', 'edit'); // bool
```

Checks run from the connector to the connectable, and permissions()->has() is the same check as the trait’s hasPermissionThroughConnection(). While enforce\_active\_on\_check is on (the default), a pending, blocked or expired connection grants nothing. Checks are memoised per pair for the request — pass force: true to the trait method to bypass the cache (see Caching).

permissions()->all() and the trait’s permissionsThroughConnection() return the stored set regardless of status or expiry — use the check methods for access decisions.

## Wildcard permissions

A connection whose permission set contains \* passes any permission check, and a segment wildcard like posts.\* matches posts.edit. Sets without a wildcard behave exactly as before — this is purely opt-in by the stored data:

```php
Connections::between($user, $team)->withPermissions('posts.*')->connect();
$user->hasPermissionThroughConnection($team, 'posts.edit');  // true
$user->hasPermissionThroughConnection($team, 'users.edit');  // false

Connections::between($admin, $team)->withPermissions('*')->connect();
$admin->hasPermissionThroughConnection($team, 'anything');   // true
```

The withPermission() scope and connectionsWithPermission() honour wildcards too: they match the exact permission, \* and trailing segment wildcards such as posts.\* or posts.comments.\*. Any other pattern shape — posts.\*.edit, for example — is honoured by hasPermission() and hasPermissionThroughConnection() only.

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