Every state change dispatches a plain event in RoundlyConsulting\Teams\Events, so you can send notifications, write audit logs or sync other systems without forking:
| Event | Properties | Fired when |
|---|---|---|
TeamMemberAdded | $member | A new membership is created, or a removed or lapsed one is revived by a re-add ($member->wasRecentlyCreated tells them apart). |
TeamMemberRoleChanged | $member, $previousRole | A member’s role changes. |
TeamMemberDeleted | $member | A membership is removed (soft-deleted). |
InviteCreated | $invite | An invite is created. |
InviteAccepted | $invite, $member | An invite is accepted. |
InviteResent | $invite | An invite’s code and expiry are rotated. |
InviteRevoked | $invite | An invite is revoked. |
TeamOwnershipTransferred | $team, $previousOwner, $newOwner | Ownership is transferred. |
JoinRequestCreated | $joinRequest | A user requests to join. |
JoinRequestApproved | $joinRequest | A request is approved (also on auto-approval). |
JoinRequestDenied | $joinRequest | A request is denied. |
JoinRequestExpired | $request | A pending request auto-declines on expiry. |
MembershipExpiringSoon | $member | A membership lapses within the report window (--notify). |
MembershipExpired | $member | An expired membership is pruned — by teams:members:prune, Teams::members()->prune() or model:prune. |
TeamContextSet | $teams, $multiple, $restored; team() | The current team context changed — setCurrent() ($teams empty when cleared), switchTo(), setCurrentMany(), the teams.context middleware, runAs() entry and restore, or a queued job restoring its dispatcher’s context ($restored = true; not when its membership re-check fails). |
TeamMoved | $team, $parent, $previousParentKey | A team was placed under a parent or detached to a root. |
Note the property name: JoinRequestExpired carries $request, the other join-request events carry $joinRequest. With the permissions bridge on, the package itself listens to TeamMemberDeleted, MembershipExpired and TeamMemberAdded (revived rows only) to forget the member’s grants in that team.
use Illuminate\Support\Facades\Event;
use RoundlyConsulting\Teams\Events\InviteAccepted;
use RoundlyConsulting\Teams\Events\TeamMemberRoleChanged;
Event::listen(function (InviteAccepted $event): void {
logger()->info('Invite accepted', [
'invite' => $event->invite->id,
'member' => $event->member->id,
]);
});
Event::listen(function (TeamMemberRoleChanged $event): void {
// $event->member->role is the new role; $event->previousRole the old one
});Notification bridge
Publish teams-stubs to get App\Listeners\TeamEventSubscriber — a queued subscriber with handlers for TeamMemberAdded, TeamMemberRoleChanged and InviteCreated. Drop your own Notification classes into its handlers; it runs on notifications.queue_connection, falling back to queue.default. Register it:
// app/Providers/AppServiceProvider.php → boot()
use App\Listeners\TeamEventSubscriber;
use Illuminate\Support\Facades\Event;
Event::subscribe(TeamEventSubscriber::class);Extend its subscribe() map to react to any other event. The package ships the wiring; your app owns the content.
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 cryptoBy donating, you agree to our donation terms.
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.