stats4sd / filament-team-management
Teams, optional programs and membership invitations for Laravel Filament with host-owned authorization
Package info
github.com/stats4sd/filament-team-management
pkg:composer/stats4sd/filament-team-management
Fund package maintenance!
Requires
- php: ^8.4
- althinect/filament-spatie-roles-permissions: ^3.x-dev
- awcodes/shout: ^3.0
- filament/filament: ^4.2
- livewire/livewire: ^3.6.4
- spatie/laravel-package-tools: ^1.15.0
Requires (Dev)
- larastan/larastan: v3.8.0
- laravel/pint: ^1.0
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0
- pestphp/pest: ^4.1.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^v4.0.0
- phpstan/extension-installer: ^1.1
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- spatie/laravel-ray: ^1.26
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-dev
- v4.0.7
- v4.0.6
- v4.0.5
- v4.0.4
- v4.0.3
- v4.0.2
- v4.0.1
- v4.0
- v2.1
- v2.0
- v1.05
- v1.04
- v1.03
- v1.02
- v1.1
- v1.0
- dev-membership-only
- dev-a7-rename-program-teams
- dev-a6-program-columns-always
- dev-a5-declare-spatie-permission
- dev-a4-dead-code-removal
- dev-a3-readme-env-vars
- dev-a2-changelog-upgrade
- dev-fix-2.3-members-tab-hides-admins
- dev-pr4-auth-flow
- dev-pr5-seeder-permissions
- dev-pr2-invite-morph-names
- dev-pr3-ui-wiring
- dev-pr1-config-key-alignment
- dev-main
- dev-fix-invites-submodule
- dev-apni-fixes
This package is auto-updated.
Last update: 2026-09-18 13:32:23 UTC
README
Teams, optional programs, direct memberships and membership invitations for Laravel 13, Filament 5, Livewire 4 and PHP 8.4+. The package owns membership data and workflows; your application owns authorization, panel admission and tenant access. It does not install a permission backend or define administrator roles.
composer require stats4sd/filament-team-management php artisan filament-team-management:install
Follow SETUP.md to register your models, policies and panels. Version 5 changes the authorization boundary; see UPGRADE.md before adopting it. The installer publishes fresh-install migrations and optionally deny-default host policy stubs. It preserves existing policies and never registers a competing package policy.
Membership and authorization
Team::users() and Program::users() return all direct members. members() is an unfiltered alias. Programs and teams are many-to-many; program membership does not automatically grant access to linked teams. No admins(), isAdmin(), is_admin pivot, role resources or role-only invitations are provided.
Use the shared actions from UI, jobs and application code. Every management action requires an identified actor and authorizes against the actual configured target model, including when an Admin screen is managing a different team from the selected tenant.
use Stats4sd\FilamentTeamManagement\Actions\AddMember; use Stats4sd\FilamentTeamManagement\Actions\SendMembershipInvitation; app(AddMember::class)->handle($actor, $team, $member); $result = app(SendMembershipInvitation::class)->handle($actor, $team, 'member@example.org');
A missing policy or ability denies the operation. Filament uses the same ability arguments for presentation; shared actions reauthorize after locking. Read-only members can view permitted member lists and leave without permission to edit the tenant. App member management uses email invitations; Admin and Program screens also support a host-scoped existing-user picker.
The authorization and workflow guide contains the full abilities table, simple administrator/member policy, scoped-permission recipe, transactional participants, events and lifecycle rules.
Invitations and mail
An email invitation to a new user creates a pending Invite and queues mail after commit. An email invitation to an existing user requires both inviteMember and addMember, immediately adds membership and sends an update notification. It does not create a synthetic accepted Invite. Existing-user pending acceptance is not implemented.
InvitationResult::status is invitation_created, member_added, duplicate_pending, expired_pending, already_member or skipped_blank. Invalid input and denied actions throw. Expired pending invitations can be renewed with Resend; it rotates the token. Cancel deletes only pending invitations. Accepted invitations remain history. Pending counts exclude accepted and expired invitations.
Mail is queued by default; run your application's queue worker. Set queue_mail to false for synchronous transport, still after commit. A saved invitation is not proof of delivery. Results expose mailStatus (pending_commit, queued, sent, failed) and an error on dispatch failure. A failed resend dispatch raises InvitationDeliveryFailed after the new token is saved. Queue retries and provider delivery are host operational responsibilities. Message snapshots retain their original token/link and sender text; a later resend does not rewrite an older queued message. Rotated/cancelled links remain invalid even if old mail arrives later.
Configuration
Publish config with php artisan vendor:publish --tag=filament-team-management-config. Display names, panel IDs, participant classes, picker class, expiry and fallback route are plain config values. Panel IDs identify existing host panels and do not set their URL paths or register panels. The configured App panel must also be the default authentication panel.
| Config | Default | Purpose |
|---|---|---|
names.team, names.program, names.user |
team, program, user |
Singular display words; independent of class/table names |
panels.app, panels.program, panels.admin |
app, program, admin |
Host panel IDs used for links and redirects |
invite_expiry_days |
null |
No expiry by default; integer days applies to new/resend invitations |
participants |
[] |
Ordered synchronous MembershipParticipant classes |
user_picker |
null |
UserPicker class; no candidates until configured |
no_memberships_route |
null |
Named authenticated, tenant-independent host landing route; otherwise package no-memberships page |
Environment variables
The installer reads your configured values and adds missing keys independently to .env and .env.example. It preserves existing whole-key assignments, including the first line. Namespace values use dotenv-compatible quoting.
Variable suffix after FILAMENT_TEAM_MANAGEMENT_ |
Default |
|---|---|
USE_PROGRAMS |
false |
QUEUE_MAIL |
true |
USER_MODEL |
Package Models\User; installer uses your auth provider model |
TEAM_MODEL, PROGRAM_MODEL |
Package Models\Team, Models\Program |
USER_TABLE, TEAMS_TABLE, PROGRAMS_TABLE |
users, teams, programs |
INVITES_TABLE |
invites |
TEAM_MEMBERS_TABLE, PROGRAM_MEMBERS_TABLE, PROGRAM_TEAM_TABLE |
team_members, program_members, program_team |
USER_FOREIGN_KEY, TEAMS_FOREIGN_KEY, PROGRAMS_FOREIGN_KEY |
user_id, team_id, program_id |
Development
composer test
composer analyse
vendor/bin/pint --test
The test host explicitly implements policies and tenant access without a permission package. Production row-lock tests are opt-in and use MySQL with InnoDB, matching the primary database used by consuming applications. Each test creates and drops its own uniquely named database; use a test server and credentials with permission to create and drop databases. PHP needs the pdo_mysql, pcntl and posix extensions. SQLite tests do not establish row-lock behavior.
FTM_TEST_MYSQL_HOST=127.0.0.1 FTM_TEST_MYSQL_PORT=3306 FTM_TEST_MYSQL_USER=root vendor/bin/pest tests/Concurrency --compact --colors=never
Set FTM_TEST_MYSQL_PASSWORD if the test account requires a password. The host defaults to 127.0.0.1 and the user to root; setting FTM_TEST_MYSQL_PORT enables the tests. Without it, the four concurrency tests are skipped. See tests/Concurrency/MembershipConcurrencyTest.php and the implementation log for coverage and verification results.