---
title: "Members — Teams for Laravel | Roundly"
description: "Add, remove and inspect members; one row per member, idempotent adds that revive removed members, role-change events and soft-delete removal."
url: https://roundly-consulting.com/open-source/docs/teams-for-laravel/members
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)

# Members

Add, remove and inspect members through the team handle — or the Team model shorthand, which runs the same manager call. Roles are plain string keys:

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

$members = Teams::for($team)->members();

$member  = $members->add($user, 'member');                          // Member
$member  = $members->add($user, 'editor', meta: ['source' => 'import']);
$removed = $members->remove($user);   // bool — soft-deletes; false when not a member

$members->find($user);   // ?Member
$members->has($user);    // bool
$members->all();         // Collection<Member> — expired memberships included

$team->memberHasRole($user, 'editor');             // bool
$team->memberHasPermission($user, 'posts.edit');   // bool

// Model shorthand — the same manager call:
$team->addMember($user, 'member');
$team->removeMember($user);
```

## Idempotent adds

A team holds one membership row per member — a unique index that covers soft-deleted rows too. Adding is idempotent: re-adding an active member returns the existing membership and updates the role only when it differs — no duplicate rows, no defensive checks in your code. An expiresAt is overwritten only when you pass one, and a membership’s original accepted invite is never rewritten.

Re-adding a member who was removed, or whose membership expired, revives that row as a fresh membership — role, meta and expiry come from the new call (no expiresAt means permanent) — and fires TeamMemberAdded. It takes a seat, so MaxSeats applies:

```php
$members = Teams::for($team)->members();

$members->add($user, 'member');   // creates the membership — fires TeamMemberAdded
$members->add($user, 'member');   // no-op — returns the same Member
$members->add($user, 'admin');    // updates the role — fires TeamMemberRoleChanged

$members->remove($user);          // soft-deletes the row
$members->add($user, 'member');   // revives that row as a fresh membership — fires TeamMemberAdded
```

Adds run in a transaction that locks the team row, so simultaneous adds — a double submit, two people accepting the last seat — serialise: one row per member, and the seat cap holds.

## Changing roles and removing

```php
Teams::for($team)->members()->changeRole($bob, 'admin');   // Member — throws MemberNotFoundException for a non-member
Teams::for($team)->members()->remove($bob);                // bool — soft-deletes the membership

$team->findMember($bob)?->removeFromTeam();                // bool — the same, from the Member side
```

A role change fires TeamMemberRoleChanged (with the previous role) only when the role actually changes; changing the role of a model that isn’t a member throws MemberNotFoundException. Removal fires TeamMemberDeleted and is a soft delete — the row stays for audits, and adding the member again revives it.

## The Member model

```php
$member = $team->findMember($user);

$member->role;             // 'admin' — the stored role key
$member->role();           // ?Role — resolved (per-team aware); null once expired
$member->member;           // the member model (MorphTo)
$member->team;             // the Team
$member->meta;             // Collection
$member->expires_at;       // ?Carbon
$member->isExpired();      // bool
$member->acceptedInvite;   // the Invite this member joined through, or null
```

## Seat limits

When a team has a MaxSeats setting, adding a new member — or reviving a removed or expired one — throws TeamsException once the active membership count reaches the cap. Re-adding an active member never trips it — see Team settings.

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