tomatophp/filament-tenancy
Filament Tenancy by TomatoPHP adds multi-tenancy to Filament panels: switch tenants, scope resources and queries per tenant, and manage tenant records with straightforward configuration for Laravel apps.
Architecture fit. This is a Filament UI layer over stancl/tenancy — tenant/member management resources, tenant-aware panels, resource scoping, domain or path resolution. It is not a tenancy engine, and adopting it does not commit us to a novel isolation model; it commits us to stancl/tenancy's database-per-tenant model plus a thin admin surface. That is the right framing for a build-vs-buy call: the underlying architecture is already the industry default, and what we're actually evaluating is whether someone else's admin plumbing saves us a sprint. If we're already on Filament, fit is strong. If we're not, this package is not a reason to adopt Filament.
Integration feasibility. High for a timeboxed spike. The install path is conventional (composer require, publish config and migrations, migrate the central connection, register the plugin on the tenant panel). The real work is not installation but two configuration files — config/tenancy.php (connections, tenancy_db_name resolution, bootstrappers, seeder_parameters) and config/filament-tenancy.php (tenant model, routing, UI behaviour) — plus splitting migrations into central and tenant directories. The v5 release specifically reduced integration risk by moving owner creation inside $tenant->run() so it works across all database drivers, preserving the operator-supplied Unique ID as the tenant id, and decoupling tenant seeding from the application DatabaseSeeder. Those three fixes remove the most common failure modes from the creation flow.
Technical risk. Code risk is low; lifecycle risk is moderate-to-high. The package has 62 stars and zero dependents, which means we are effectively the QA function for anything beyond the happy path. It ships breaking changes on a real cadence (v4→v5 changed the owner-write flow and the plugin class name), and version lines are managed as branches (v3 is a branch switch, not a composer constraint) — a governance detail that will bite anyone assuming semver. Separate from the package, the model carries inherent operational risk: cache, session, and file storage isolation are not solved here; Octane and long-lived queue workers require explicit tenancy()->end() plus connection purge; config:cache freezes a placeholder tenant connection; and the tenant id is whatever an operator typed, which becomes a database name and URL segment.
Key questions.
stancl/tenancy dependency dominate?Stack fit. Treat the version matrix as a hard gate, not a preference: Filament 5, Livewire 4, Laravel 12 or 13, PHP 8.2+, stancl/tenancy 3.9+. Pin both tomatophp/filament-tenancy and stancl/tenancy to exact versions in composer.json. On Filament 3, we install from the v3 branch and accept that we are on a maintenance line, not the mainline.
Migration path. From a single shared, row-scoped database, the credible path is incremental: pilot one panel, one tenant, end to end, then expand. No big-bang rewrite. The vertical slice to prove is: central admin creates a tenant → provisioning creates the database and runs tenant migrations → $tenant->run() writes the owner → visiting the tenant route initialises the tenant → queries hit the tenant database. If that loop works with one tenant, everything else is elaboration. If it doesn't, we've spent days, not a quarter, finding out.
Compatibility. Two contracts run in parallel and must not be conflated. Filament's native tenancy (getTenants() / HasTenants) is relationship-based and must resolve on the central connection, while the panel's actual data queries resolve on the tenant connection. The package doesn't reconcile those for us. Likewise, it doesn't provide per-tenant cache prefixes, session isolation, or storage roots — those are ours to configure at storage/app/tenants/{id} and in the bootstrappers. Any v4 isolation gap carries straight into v5.
Sequencing.
/app/{tenant}) locally.TenantManager action class — one place to patch when v6 shifts the flow again.tenants:migrate wrapper that logs per-tenant failures and continues rather than aborting mid-rollout, and deletion hooks that drop the database, domain rows, and files.Maintenance. We own it. Budget for reading release notes before every bump, and assume the seam between "package" and "our code" needs to be a single class we control. The Tenant model is meant to be extended (plan, status, trial columns — never the id), and $tenant->run() plus the stancl bootstrappers are the sanctioned extension points. Anything we build outside those points will cost us on upgrade.
Support. No vendor, no SLA, single-maintainer bus factor, MIT license. The realistic answer to "who fixes this at 2am" is us. That is acceptable for admin-surface plumbing and unacceptable if we let it become load-bearing on a revenue-critical path without a fork plan.
Scaling. Database-per-tenant shifts cost from query complexity to per-tenant operations: provisioning latency at signup, migration runtime that grows linearly with the fleet, per-tenant backup and point-in-time recovery, per-tenant deletion on churn, and open-connection/file-descriptor pressure per worker. These are the numbers we need before quoting an isolation tier — and the reason to pilot rather than estimate.
Failure modes to design against.
tenancy.seeder_parameters.--class. Unset means creation succeeds with an empty database and no error — a guaranteed ticket generator.$tenant->run() or explicitly initialise tenancy; queues must carry the tenant id in the payload because long-lived workers stay on the previous tenant's connection.RequestTerminated / JobProcessed cleanup calling tenancy()->end() and DB::purge(). If we can't guarantee that, we don't put Octane in front of tenant panels.database value.tenancy.* routes; a custom catch-all breaks it silently.Ramp-up. Roughly one engineer-day to a working single-tenant vertical slice, plus two to three days to harden provisioning, queue handling, deletion, and fleet migrations behind a TenantManager. The steeper cost is conceptual, not technical: the team must internalise that the tenant id is immutable and operator-supplied, that tenant rows have no database column (it lives in the data JSON as tenancy_db_name), that seeding is opt-in, and that auth resolution and data resolution happen on different connections. Write the debugging checklist into the runbook — tenant initialised?, id matches URL?, tenant connection actually reached the PDO?, default connection vs. model connection agree?, seeder class set?, middleware ordered before panel boot? — and the on-call load drops sharply.
How can I help you explore Laravel packages today?