---
title: "Invitations & status — Connections for Laravel | Roundly"
description: "Model approval flows: create pending connections, accept or block them from either side, and use the ConnectionStatus enum helpers."
url: https://roundly-consulting.com/open-source/docs/connections-for-laravel/invitations
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)

# Invitations & status

Every connection has a status — pending, accepted or blocked. connect() creates accepted connections by default; invite() creates a pending one that the receiving side accepts or blocks:

```php
// Connector side: send an invitation (a pending connection).
Connections::between($user, $team)->withPermissions('view')->invite();

// Receiving side accepts or blocks.
$team->acceptConnectionFrom($user);  // status → accepted (now active)
$team->blockConnectionFrom($user);   // status → blocked (grants nothing)

// Or drive it from the connector / builder.
Connections::between($user, $team)->accept();
Connections::between($user, $team)->block();
$user->inviteConnection($team);

// Model helpers.
$connection->isPending();
$connection->isAccepted();
$connection->isBlocked();
$connection->isActive();   // accepted AND not expired
```

Only active connections — accepted and not expired — grant permissions and count for isConnectedTo() while enforce\_active\_on\_check is on (the default). A pending invitation or a blocked connection grants nothing.

- acceptConnectionFrom() and blockConnectionFrom() are called on the receiving side with the connector as the argument.
- accept() and block() throw ConnectionNotFound when the pair has no connection.
- Both are no-ops without an event when the connection is already in that state; otherwise they fire ConnectionAccepted or ConnectionBlocked.
- accept() is the one explicit way to lift a block — acceptConnectionFrom() on the receiving side.
- Creating a pending connection fires ConnectionCreated followed by ConnectionInvited.

## Status transitions are enforced

Status only moves the way ConnectionStatus::canTransitionTo() allows, and every action enforces it:

- connect(), toggle(), connectAll() and sync() never change an existing connection’s status — re-connecting can’t self-accept a pending invitation, demote an accepted one or lift a block.
- invite() over a pending invitation is fine; over an accepted or blocked connection it throws RoundlyConsulting\\Connections\\Exceptions\\InvalidStatusTransition.
- A block is lifted only by an explicit accept(). It also survives disconnect() and connections:prune: re-connecting a soft-deleted blocked pair restores it still blocked, firing ConnectionRestored.

```php
// Re-connecting never changes an existing connection's status.
Connections::between($user, $team)->invite();    // pending
Connections::between($user, $team)->connect();   // still pending — no self-accept
Connections::between($user, $team)->invite();    // fine — re-inviting a pending invitation

$team->acceptConnectionFrom($user);              // accepted
Connections::between($user, $team)->invite();    // throws InvalidStatusTransition

// Only an explicit accept() lifts a block — it even survives disconnect() and a prune.
$team->blockConnectionFrom($user);
Connections::between($user, $team)->disconnect();
Connections::between($user, $team)->connect();   // restored still blocked (ConnectionRestored)
$team->acceptConnectionFrom($user);              // accepted — the block is lifted
```

## Invitations by default

Set default\_status to pending to make every new connection an invitation — connect() then creates pending connections and fires ConnectionInvited for each. For a single call, asPending() stages the pending state:

```php
// config/connections.php — every new connection starts as an invitation
'default_status' => 'pending',

// Or for a single call: asPending() stages the pending state.
Connections::between($user, $team)->asPending()->withPermissions('view')->connect();
// same as ->invite()
```

## The ConnectionStatus enum

The status column is cast to the string-backed Enums\\ConnectionStatus enum. Its canTransitionTo() guard allows accepting from pending or blocked (accept() on a blocked link is the explicit unblock), blocking from any state, moving back to pending never, and a transition to the current state always. Every action enforces it, and the connect-side verbs are stricter still — they never lift a block. Use it in your own UI and validation too:

```php
use RoundlyConsulting\Connections\Enums\ConnectionStatus;

$connection->status;   // ConnectionStatus::Accepted (cast from the status column)

ConnectionStatus::Pending->canTransitionTo(ConnectionStatus::Accepted);   // true
ConnectionStatus::Blocked->canTransitionTo(ConnectionStatus::Accepted);   // true — accept() is the explicit unblock
ConnectionStatus::Accepted->canTransitionTo(ConnectionStatus::Blocked);   // true — blocking is always allowed
ConnectionStatus::Accepted->canTransitionTo(ConnectionStatus::Pending);   // false
```

The enum uses the shared Helpers trait from enums-for-laravel, so it also ships labels, select options and a validation rule:

```php
use RoundlyConsulting\Connections\Enums\ConnectionStatus;

ConnectionStatus::values();          // Collection: ['pending', 'accepted', 'blocked']
ConnectionStatus::labels();          // Collection: ['Pending', 'Accepted', 'Blocked']
ConnectionStatus::toOptions();       // Collection: ['pending' => 'Pending', ...] for <select>
ConnectionStatus::toArray();         // array: the plain-array form of toOptions()
ConnectionStatus::options();         // Collection<EnumOption> for JS/Inertia selects
ConnectionStatus::validationRule();  // 'in:pending,accepted,blocked'
ConnectionStatus::Accepted->readable();       // 'Accepted'
ConnectionStatus::tryFromLabel('Accepted');   // ConnectionStatus::Accepted

$request->validate([
    'status' => ['required', ConnectionStatus::validationRule()],
]);
```

## 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)
