PHPackages                             guidance-studio/filament-tenant-members - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Admin Panels](/categories/admin)
4. /
5. guidance-studio/filament-tenant-members

ActiveLibrary[Admin Panels](/categories/admin)

guidance-studio/filament-tenant-members
=======================================

Multi-tenant organization, member and invitation management for Laravel Filament

v0.1.0(3w ago)1187MITPHPPHP ^8.3

Since May 19Pushed 1w agoCompare

[ Source](https://github.com/GuidanceStudio/filament-tenant-members)[ Packagist](https://packagist.org/packages/guidance-studio/filament-tenant-members)[ RSS](/packages/guidance-studio-filament-tenant-members/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (9)Versions (6)Used By (0)

Filament Members
================

[](#filament-members)

Multi-tenant organization, member and invitation management for Laravel Filament.

Provides a complete member/invite system for Filament v5 multi-tenant panels, plus an admin panel for superadmin user/organization management. Works out-of-the-box with zero configuration, or fully customizable with your own models and role enums.

Requirements
------------

[](#requirements)

- PHP 8.3+
- Laravel 13+
- Filament 5+

Installation
------------

[](#installation)

```
composer require guidance-studio/filament-tenant-members
```

Run the migrations:

```
php artisan migrate
```

This creates three tables (`organizations`, `organization_user` pivot, and `organization_invites`) and adds a `role` column to the existing `users` table for system-level (landlord) roles. The column defaults to the value of `default_role` in the config.

Quick Start
-----------

[](#quick-start)

### 1. User Model

[](#1-user-model)

Add the `HasOrganizations` trait to your User model:

```
use Guidance\FilamentTenantMembers\Concerns\HasOrganizations;
use Filament\Models\Contracts\FilamentUser;
use Filament\Models\Contracts\HasTenants;

class User extends Authenticatable implements FilamentUser, HasTenants
{
    use HasOrganizations;

    public function canAccessPanel(Panel $panel): bool
    {
        if ($panel->getId() === 'admin') {
            return $this->role === 'admin'; // your logic here
        }

        return true;
    }
}
```

### 2. Organization Model (optional)

[](#2-organization-model-optional)

The package provides its own `Organization` model that works out-of-the-box. If you need to extend it, create your own model with the `IsOrganization` trait:

```
use Guidance\FilamentTenantMembers\Concerns\IsOrganization;

class Organization extends Model
{
    use IsOrganization;

    // your custom methods...
}
```

Then update the config to point to your model (see [Configuration](#configuration)).

### 3. Organization Panel

[](#3-organization-panel)

Register the plugin and the package's pages in your `OrganizationPanelProvider`:

```
use Guidance\FilamentTenantMembers\FilamentTenantMembersPlugin;
use Guidance\FilamentTenantMembers\Filament\OrganizationPanel\Pages\Auth\Register;
use Guidance\FilamentTenantMembers\Filament\OrganizationPanel\Pages\Tenancy\RegisterOrganization;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...your panel config...
        ->registration(Register::class)
        ->tenant(Organization::class, slugAttribute: 'slug')
        ->tenantRegistration(RegisterOrganization::class)
        ->plugin(FilamentTenantMembersPlugin::make());
}
```

### 4. Admin Panel

[](#4-admin-panel)

Register the admin plugin in your `AdminPanelProvider`:

```
use Guidance\FilamentTenantMembers\FilamentTenantMembersAdminPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...your panel config...
        ->plugin(FilamentTenantMembersAdminPlugin::make());
}
```

That's it. You now have:

- A **Settings** cluster in the organization panel sidebar with:
    - **General** — edit organization name and slug
    - **Members** — member list and invite management
- An invite acceptance flow via email (handles both existing and new users)
- **User** and **Organization** resources in the admin panel

Features
--------

[](#features)

### Settings Cluster

[](#settings-cluster)

The plugin adds a **Settings** cluster to the organization panel sidebar with two sub-pages: General and Members. External packages or the application itself can add their own pages to the cluster by declaring:

```
use Guidance\FilamentTenantMembers\Filament\OrganizationPanel\Clusters\Settings;

class BillingSettings extends Page
{
    protected static ?string $cluster = Settings::class;
    // ...
}
```

Register the page via `->pages([BillingSettings::class])` in your panel provider or another plugin and it will appear automatically in the Settings sub-navigation.

### General Settings Page

[](#general-settings-page)

Accessible at **Settings → General**. Provides a form to edit the organization's name and slug with validation (slug format + uniqueness).

### Members Page

[](#members-page)

Accessible at **Settings → Members**. Contains two tabs:

- **Members** — lists all organization members with their roles. Members with the `canManageMembers()` permission can change roles, transfer ownership, and remove members. Protected roles (like Owner) cannot be modified or removed. Non-owner members can leave the organization from their own row.
- **Pending Invitations** — lists active invitations with options to resend or cancel. Shows a badge count of pending invites. The resend button is hidden during the cooldown period (default: 5 minutes) to prevent spam.

The **Invite Users** button is only visible to members whose role returns `true` from `canInviteMembers()`. It opens a modal where you can invite multiple users at once, each with a specific role.

### Role-Based Permissions

[](#role-based-permissions)

Access control is driven entirely by the role enum. Each role defines its own permissions via the `RoleEnum` contract:

MethodPurposeDefault (Owner)Default (Admin)Default (User)`canInviteMembers()`Can see and use the "Invite Users" buttonYesYesNo`canManageMembers()`Can change roles and remove membersYesYesNo`isProtected()`Cannot be modified or removed by othersYesNoNo`ownerValue()`Role value assigned to the organization creator———When you create a custom role enum, you define these permissions per role. No config needed.

### Leave Organization

[](#leave-organization)

Non-owner members see a **Leave** button on their own row in the members table. Clicking it detaches them from the organization immediately and redirects to the Filament home URL. Members with a protected role (e.g., Owner) cannot leave — they must transfer ownership first.

### Transfer Ownership

[](#transfer-ownership)

The organization owner can transfer ownership to another member via the action menu. This:

1. Assigns the `ownerValue()` role to the target member.
2. Demotes the current owner to the `default_role` (e.g., User).

The transfer ownership action is only visible to members whose role returns `true` from `isProtected()`.

### Resend Cooldown

[](#resend-cooldown)

After an invitation is resent, the **Resend** button is hidden for a configurable cooldown period (default: 5 minutes) to prevent email spam. Configure via `resend_cooldown_minutes` in the config file.

### Invite Flow

[](#invite-flow)

1. A member with invite permissions sends an invitation from the Members page.
2. The invitee receives an email with an accept link (`/invite/{token}`).
3. Clicking the link routes through the `AcceptInviteController`:
    - **Logged-in user with matching email**: redirected to the `AcceptInvite` page, which shows the organization name and inviter. The user can accept or decline.
    - **Logged-in user with a different email**: redirected to the `AcceptInvite` page, which shows an email mismatch error with a logout button.
    - **Existing user, not logged in**: the token is stored in the session and the user is redirected to login. After login, the `AcceptInvite` page is opened automatically via the `url.intended` mechanism.
    - **New user**: the token and email are stored in the session and the user is redirected to registration. The email field is pre-filled and locked. After registration, the `AcceptInvite` page is opened automatically.
4. On the `AcceptInvite` page, the user clicks **Accept invitation** or **Decline**. Accepting adds the user to the organization with the invited role and redirects to the organization panel.

Invitations expire after a configurable number of days (default: 7). Expired invitations can be resent, which resets the expiration.

### Registration Page

[](#registration-page)

The package provides a custom registration page (`Register`) that:

- Pre-fills the email field when a user arrives via an invite link.
- Disables the email field so the user cannot change it.
- Otherwise behaves identically to the default Filament registration page.

### Tenancy Pages

[](#tenancy-pages)

- **RegisterOrganization** — form to create a new organization (name + slug). The slug is auto-generated from the name. The creator is assigned the role returned by `ownerValue()`.

### Admin Panel

[](#admin-panel)

- **UserResource** — list, filter, and edit all users. Displays system role (landlord role), email verification status, and creation date.
- **OrganizationResource** — list and edit all organizations. Displays member count and creation date.

Configuration
-------------

[](#configuration)

Publish the config file:

```
php artisan vendor:publish --tag=filament-tenant-members-config
```

This creates `config/filament-tenant-members.php`:

```
return [
    // Model classes
    'models' => [
        'user' => \App\Models\User::class,
        'organization' => \Guidance\FilamentTenantMembers\Models\Organization::class,
    ],

    // Role enum for organization roles (Owner, Admin, User)
    // Must implement Guidance\FilamentTenantMembers\Contracts\RoleEnum
    'role_enum' => \Guidance\FilamentTenantMembers\Enums\DefaultRole::class,

    // Role enum for system-level roles (Admin, User)
    // Used in the admin panel's UserResource
    // Must implement Filament\Support\Contracts\HasLabel
    'landlord_role_enum' => \Guidance\FilamentTenantMembers\Enums\DefaultLandlordRole::class,

    // Default role assigned to new members via invite
    'default_role' => 'user',

    // Days until an invitation expires
    'invite_expires_days' => 7,

    // Maximum number of invitations per batch
    'max_invites_per_batch' => 5,

    // Minutes before an invitation can be resent
    'resend_cooldown_minutes' => 5,

    // Attribute used for tenant URL slugs
    'tenant_slug_attribute' => 'slug',

    // Filament panel ID for the organization panel
    'panel_id' => 'organization',

    // Route configuration for the invite acceptance endpoint
    'routes' => [
        'prefix' => 'invite',              // URL: /invite/{token}
        'middleware' => ['web', 'throttle:10,1'],
    ],
];
```

### Custom Role Enum

[](#custom-role-enum)

The package ships with a `DefaultRole` enum (Owner, Admin, User) that works without any configuration. To use your own roles, create an enum that implements the `RoleEnum` contract:

```
use Guidance\FilamentTenantMembers\Contracts\RoleEnum;

enum OrganizationRole: string implements RoleEnum
{
    case Owner = 'owner';
    case Admin = 'admin';
    case Editor = 'editor';
    case Viewer = 'viewer';

    public function getLabel(): string
    {
        return match ($this) {
            self::Owner => 'Owner',
            self::Admin => 'Admin',
            self::Editor => 'Editor',
            self::Viewer => 'Viewer',
        };
    }

    public function getColor(): string
    {
        return match ($this) {
            self::Owner => 'warning',
            self::Admin => 'info',
            self::Editor => 'success',
            self::Viewer => 'gray',
        };
    }

    public function canInviteMembers(): bool
    {
        return match ($this) {
            self::Owner, self::Admin => true,
            default => false,
        };
    }

    public function canManageMembers(): bool
    {
        return match ($this) {
            self::Owner, self::Admin => true,
            default => false,
        };
    }

    public function isProtected(): bool
    {
        return $this === self::Owner;
    }

    public static function ownerValue(): string
    {
        return self::Owner->value;
    }

    public static function assignableOptions(): array
    {
        return collect(self::cases())
            ->reject(fn (self $role) => $role->isProtected())
            ->mapWithKeys(fn (self $role) => [$role->value => $role->getLabel()])
            ->all();
    }

    public static function orderBySql(string $column): string
    {
        $cases = collect(self::cases())
            ->map(fn (self $role, int $index) => "WHEN '{$role->value}' THEN {$index}")
            ->implode(' ');

        return "CASE {$column} {$cases} ELSE " . count(self::cases()) . ' END';
    }
}
```

Then set it in your config:

```
'role_enum' => \App\Enums\OrganizationRole::class,
```

### RoleEnum Contract Reference

[](#roleenum-contract-reference)

The `RoleEnum` interface extends Filament's `HasLabel` and `HasColor` and requires these methods:

MethodTypeDescription`getLabel()`instanceDisplay name for the role (badge text)`getColor()`instanceBadge color (`warning`, `info`, `gray`, etc.)`canInviteMembers()`instanceWhether this role can invite new members`canManageMembers()`instanceWhether this role can change roles and remove members`isProtected()`instanceWhether this role cannot be modified or removed`ownerValue()`staticThe role value assigned to organization creators`assignableOptions()`staticRoles available in the invite form and role change modal`orderBySql()`staticSQL CASE expression for ordering members by role priority### Custom Landlord Role Enum

[](#custom-landlord-role-enum)

The `landlord_role_enum` is used by the admin panel's `UserResource` to display and filter users by their system-level role. The default provides Admin and User. To customize:

```
'landlord_role_enum' => \App\Enums\LandlordRole::class,
```

Your enum must implement `Filament\Support\Contracts\HasLabel`.

### Custom Organization Model

[](#custom-organization-model)

If you need additional fields or methods on the Organization model:

```
use Guidance\FilamentTenantMembers\Concerns\IsOrganization;

class Organization extends Model
{
    use IsOrganization;

    // Add your custom fields, relationships, etc.
}
```

Update the config:

```
'models' => [
    'organization' => \App\Models\Organization::class,
],
```

Publishing
----------

[](#publishing)

The package works out-of-the-box without publishing anything. Migrations are loaded automatically, views are served from the package, and the default config covers common use cases. Publishing is only needed when you want to customize something.

Publish everything at once:

```
php artisan vendor:publish --provider="Guidance\FilamentTenantMembers\FilamentTenantMembersServiceProvider"
```

Or publish individually by tag:

### Config

[](#config)

```
php artisan vendor:publish --tag=filament-tenant-members-config
```

Publishes `config/filament-tenant-members.php`. Required if you want to use custom role enums, custom models, or change any default settings. See [Configuration](#configuration) for all available options.

### Views

[](#views)

```
php artisan vendor:publish --tag=filament-tenant-members-views
```

Publishes the invitation email Blade template to `resources/views/vendor/filament-tenant-members/mail/organization-invite.blade.php`. Useful if you want to customize the email layout, wording, or branding.

### Migrations

[](#migrations)

```
php artisan vendor:publish --tag=filament-tenant-members-migrations
```

Publishes the migration files to `database/migrations/`. The package creates three tables:

- `organizations` — stores organization name and slug
- `organization_user` — pivot table with role column
- `organization_invites` — invitation tokens, roles, and expiration

A fourth migration adds a `role` column to the existing `users` table to store the system-level (landlord) role.

Migrations are loaded automatically from the package, so publishing is only needed if you want to modify the table structure (e.g., add extra columns to `organizations`). If you publish, the package will skip its own copies to avoid duplicates.

Events
------

[](#events)

The package dispatches the following event:

- `Guidance\FilamentTenantMembers\Events\OrganizationInviteCreated` — fired when an invitation is created or resent. Contains the `OrganizationInvite` model instance.

The package registers two listeners internally:

- `SendOrganizationInviteMail` — sends the invitation email (queued).
- `AcceptPendingInvite` — listens to `Illuminate\Auth\Events\Login`. If a pending invite token is stored in the session (set before login/registration), it sets `url.intended` to the `AcceptInvite` page so the user is redirected there after authenticating.

To add custom logic when an invite is created, register your own listener for `OrganizationInviteCreated`:

```
use Guidance\FilamentTenantMembers\Events\OrganizationInviteCreated;

Event::listen(OrganizationInviteCreated::class, function (OrganizationInviteCreated $event) {
    // $event->invite — the OrganizationInvite model
});
```

Console Commands
----------------

[](#console-commands)

### Cleanup Expired Invites

[](#cleanup-expired-invites)

```
php artisan filament-tenant-members:cleanup-invites
```

Deletes all expired, unaccepted invitations (rows where `expires_at` is in the past and `accepted_at` is `null`). Schedule it from your application's `routes/console.php` (or `app/Console/Kernel.php`) if you want it to run automatically — the package does not register a schedule itself.

Package Structure
-----------------

[](#package-structure)

```
src/
├── Concerns/
│   ├── HasOrganizations.php         # Trait for User model (relationships, tenancy)
│   └── IsOrganization.php           # Trait for Organization model (relationships)
├── Contracts/
│   └── RoleEnum.php                 # Interface for custom role enums
├── Enums/
│   ├── DefaultRole.php              # Built-in organization roles
│   └── DefaultLandlordRole.php      # Built-in system roles
├── Events/
│   └── OrganizationInviteCreated.php
├── Console/Commands/
│   └── CleanupExpiredInvites.php    # Deletes expired, unaccepted invitations
├── Filament/
│   ├── OrganizationPanel/
│   │   ├── Clusters/
│   │   │   └── Settings.php         # Settings cluster (General + Members sub-nav)
│   │   └── Pages/
│   │       ├── AcceptInvite.php     # Accept/decline invitation page (simple layout)
│   │       ├── Members.php          # Members & invitations tabs (inside Settings cluster)
│   │       ├── Auth/Register.php
│   │       ├── Settings/
│   │       │   └── GeneralSettings.php  # Edit organization name & slug
│   │       └── Tenancy/
│   │           └── RegisterOrganization.php
│   └── AdminPanel/Resources/        # UserResource, OrganizationResource
├── Http/Controllers/
│   └── AcceptInviteController.php   # Routes invite link → login/register/AcceptInvite page
├── Listeners/
│   ├── SendOrganizationInviteMail.php
│   └── AcceptPendingInvite.php
├── Livewire/
│   ├── ListMembers.php              # Members table component
│   └── ListInvitations.php          # Invitations table component
├── Mail/
│   └── OrganizationInviteMail.php   # Queued mailable
├── Models/
│   ├── Organization.php             # Default organization model
│   └── OrganizationInvite.php       # Invitation model
├── FilamentTenantMembers.php              # Config helper
├── FilamentTenantMembersPlugin.php        # Organization panel plugin
├── FilamentTenantMembersAdminPlugin.php   # Admin panel plugin
└── FilamentTenantMembersServiceProvider.php

```

License
-------

[](#license)

MIT

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance97

Actively maintained with recent releases

Popularity17

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 87.5% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~11 days

Total

5

Last Release

21d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/283778462?v=4)[Guidance](/maintainers/Guidance)[@guidance](https://github.com/guidance)

---

Top Contributors

[![AndColla](https://avatars.githubusercontent.com/u/14584600?v=4)](https://github.com/AndColla "AndColla (7 commits)")[![Ymx1ZQ](https://avatars.githubusercontent.com/u/2812777?v=4)](https://github.com/Ymx1ZQ "Ymx1ZQ (1 commits)")

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/guidance-studio-filament-tenant-members/health.svg)

```
[![Health](https://phpackages.com/badges/guidance-studio-filament-tenant-members/health.svg)](https://phpackages.com/packages/guidance-studio-filament-tenant-members)
```

###  Alternatives

[a2insights/filament-saas

Filament Saas for A2Insights

191.7k](/packages/a2insights-filament-saas)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
