Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Multi Tenancy Start Kit Laravel Package

felipearnold/filament-multi-tenancy-start-kit

Laravel/Filament starter kit for multi-tenant SaaS applications. It scaffolds tenant-aware panels and onboarding, giving you a clean foundation to build secure, isolated admin experiences faster.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a starter kit, not a general-purpose library. Treat it as a reference implementation you fork/copy into an application, not as a dependency you'll keep bumping. With 64 stars and a description that is essentially Filament's own marketing blurb (it doesn't actually describe tenancy), assume documentation is thin and the code is the documentation.

Where to look first, in this order:

  1. composer.json — the require block tells you which Filament major version it targets. This decides everything: Filament v2 has no native tenancy, v3 does (->tenant() on the panel). If it pins filament/filament: ^2.x, the kit is hand-rolling scoping and you'll inherit that pattern.
  2. app/Providers/Filament/*PanelProvider.php — the ->tenant(...) call, tenant middleware, and ->tenantMiddleware([...], isPersistent: true) are the heart of the setup.
  3. database/migrations/ — look for the tenant table, the tenant_id/team_id foreign keys, and the membership pivot. This is where correctness lives or dies.
  4. app/Models/ — the tenant model and the User ↔ tenant relationship (teams(), currentTeam(), or a membership pivot with roles).
  5. tests/ — if there are none, lower your expectations; isolation bugs are invisible until production.

Minimal setup:

composer require felipearnold/filament-multi-tenancy-start-kit
php artisan vendor:publish --tag="filament-multi-tenancy-start-kit-migrations"  # check README for the real tag
php artisan migrate
php artisan make:filament-user          # or use the kit's seeder

Then register the panel provider if the package doesn't auto-discover it, create two tenants, attach your user to both, and log in.

Your first use case should be a smoke test, not a feature. Create Tenant A and Tenant B, put one record in each of a single resource, log in as a user belonging to A only, and confirm: (a) you see only A's record, (b) hitting B's record by URL returns 404/403, (c) the tenant switcher doesn't list B. Do this before writing a single line of business logic.

Implementation Patterns

Tenant resolution via the panel. The idiomatic Filament v3 approach the kit should be using:

$panel
    ->tenant(Team::class, ownershipRelationship: 'team', slugAttribute: 'slug')
    ->tenantMiddleware([
        PersistTenantMiddleware::class, // or the kit's equivalent
    ], isPersistent: true);

isPersistent: true is what makes the tenant survive navigation instead of being re-derived from the URL each request. If the kit doesn't set it, expect the tenant to reset when you click into a non-tenant-scoped page.

Scoping resources explicitly when the panel-level tenancy isn't enough. For resources where tenant_id isn't a direct column (e.g. Invoice belongs to Project belongs to Team), don't fight the automatic scoping — scope in the query:

public static function getEloquentQuery(): Builder
{
    return parent::getEloquentQuery()
        ->whereBelongsTo(Filament::getTenant());
}

And the inverse for genuinely global resources (currencies, countries, plans):

protected static bool $isScopedToTenant = false;

Be deliberate about this flag. Every resource should be a conscious yes or no.

Membership and roles are two separate concerns. The kit will give you membership (user belongs to tenant). Roles usually need a third layer: a team_user pivot with a role column, or spatie/laravel-permission with the teams feature enabled. Wire Filament's ->tenant(..., ) together with the role check in canAccessPanel() or a policy — don't rely on the UI hiding things.

Onboarding flow. The pattern that scales: registration creates a user and a personal tenant; subsequent invitations attach the user to an existing tenant. Keep the invite acceptance route outside the tenant-scoped panel (->tenantMiddleware(..., isPersistent: false) on that route, or a plain controller) so a user can accept before they have a "current" tenant.

Tenant-aware background work. Any job, command, or scheduled task touching tenant data needs explicit context:

class RecalculateStats implements ShouldQueue
{
    public function __construct(public int $tenantId) {}

    public function handle(): void
    {
        Filament::setTenant(Team::findOrFail($this->tenantId));
        // ... work
    }
}

Never rely on Filament::getTenant() inside a queue worker — it's empty there.

Tenant switching UX. Use the built-in tenant menu (->tenantMenu()), and if you need a "switch tenant" action outside the panel chrome, redirect to a tenant-scoped URL rather than trying to set session state manually. The URL is the source of truth in Filament's model.

Gotchas and Tips

Audit the migrations before running them anywhere real. A 64-star starter kit is a code sample. Check for: nullable tenant_id on tables that must be scoped, missing composite indexes (['tenant_id', 'slug'] rather than a global unique on slug), and foreign keys without cascadeOnDelete(). Orphaned rows across tenants are painful to clean up later.

Unique constraints are the classic bug. users.email unique globally means a person can't be in two tenants with separate accounts; unique per tenant means login needs tenant context before authentication. Decide explicitly, and check what the kit chose.

Prefer copying over depending. Because this is a start kit, composer require and then commit the published files into app/ and database/. You will need to modify the tenant model, the middleware, and the invitation flow — fighting a vendor package you can't cleanly override is worse than owning 400 lines of code.

Isolation leaks to test for, in order of likelihood:

  • Model::find($id) or route-model binding on a resource that has $isScopedToTenant = false — instant cross-tenant read.
  • Relation managers and Select/BelongsTo options that query the related model without a tenant filter.
  • Global search across resources where one resource forgot scoping.
  • Bulk actions and exports — these often bypass the resource query.
  • File uploads: check the disk path includes the tenant, or two tenants' logo.png collide.
  • withoutGlobalScopes() anywhere in the codebase — grep for it.

Cache and storage collisions. If the kit uses Cache::remember('settings'), two tenants share it. Prefix everything with Filament::getTenant()->getKey(), or use a tenant-aware cache driver. Same for Storage::disk('local')->put(...) paths and generated PDF/report files.

Filament version drift. Most tenancy tutorials and snippets you'll find online are v3. If the kit is v2-era, Filament::getTenant() doesn't exist and you're looking at custom middleware resolving from the route. Don't mix patterns — pick the version's idiom and stay in it.

Debugging "I can see another tenant's data":

dd(Filament::getTenant());                    // is tenancy even resolving?
dd(static::getEloquentQuery()->toSql());      // what's the actual query?

Then check middleware order in the panel provider — tenancy middleware must run before the resource query is built, and PersistTenantMiddleware needs to come after session start.

Extension points worth knowing: the panel's ->tenant() signature accepts a custom slugAttribute and ownershipRelationship; Tenant::class can be any model implementing the right contract; and ->tenantMiddleware() accepts your own classes, which is where you'd inject "redirect to tenant selection if none chosen" behavior.

Set a review budget. Before adopting, spend 30 minutes writing one cross-tenant isolation test per resource type. If the kit doesn't make that easy (factories, seeders, a test helper that logs in as a tenant member), that's a signal about how much scaffolding you'll be writing yourself — at which point installing Filament's native tenancy and writing the team model by hand may be the faster path.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin