Installation
Filament Tenancy installs like any other Composer package. Add the private registry, require the package, run the installer, and register one plugin in your Filament panel — about five minutes, then create your first tenant.
Note
The package depends on stancl/tenancy v4, which has no stable tag on Packagist yet. Nothing to do on your side — the registry serves it as a regular stable release, so a plain composer require works. How that works →
Requirements
| Requirement | Version |
|---|---|
| PHP | 8.4+ |
| Laravel | 13.x |
| Filament | 5.x (panels installed: php artisan filament:install --panels) |
| Database | SQLite, MySQL, MariaDB, PostgreSQL, or SQL Server |
Two things you'll need beyond a stock Laravel app:
- A queue worker. Tenant provisioning runs on your queue. Locally,
php artisan queue:workis enough; in production keep one running under Supervisor or Horizon. - Wildcard DNS for subdomain identification (
acme.myapp.test). Laravel Herd and Valet resolve*.myapp.testautomatically.
The database user needs permission to create and drop databases (CREATE / DROP DATABASE on MySQL and MariaDB, the CREATEDB role attribute on PostgreSQL). SQLite needs nothing.
1. Add the registry and your access token
Your Packstub dashboard's Install Guide page shows these two commands with your token filled in. The token username starts with pkg_; the secret is shown once, when the token is created.
composer config repositories.packstub-filament-tenancy composer https://packstub.dev/composer/filament-tenancy
composer config --auth http-basic.packstub.dev pkg_xxxxxxxxxxxxxxxx your-token-secret
The second command writes auth.json in your project root. Make sure it's in .gitignore (recent Laravel skeletons already exclude it).
Tip
In CI, skip auth.json and provide the credentials through a COMPOSER_AUTH secret instead. Create a separate token per environment from the dashboard.
COMPOSER_AUTH='{"http-basic":{"packstub.dev":{"username":"pkg_xxxxxxxxxxxxxxxx","password":"your-token-secret"}}}'
2. Require the package
composer require packstub/filament-tenancy
stancl/tenancy v4 is pulled in automatically.
3. Run the installer
php artisan packstub-tenancy:install
The installer asks one question — how tenants should be identified:
| Mode | Tenant URL | When to pick it |
|---|---|---|
| Subdomain (recommended) | acme.myapp.test/admin |
Each tenant gets its own host. Enables custom domains later. |
| Path | myapp.test/admin/teams/acme |
No wildcard DNS available. |
It then publishes config/packstub-tenancy.php and config/tenancy.php (stancl's config, with the plugin's defaults already applied) and offers to run the package migrations — tenants, domains, tenant_user, tenant_resources — against your central database. Say yes.
Pass --no-interaction for scripted installs; it uses subdomain mode.
Warning
Don't also publish stancl/tenancy's migrations (--tag=migrations). Filament Tenancy ships its own tenants and domains tables and the two sets would collide.
4. Prepare your User model
Users live in the central database and belong to tenants through the tenant_user pivot. Add one trait and Filament's two tenancy contracts:
use Filament\Models\Contracts\HasDefaultTenant;
use Filament\Models\Contracts\HasTenants;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Packstub\Tenancy\Concerns\HasPackstubTenants;
use Stancl\Tenancy\Database\Concerns\CentralConnection;
class User extends Authenticatable implements HasDefaultTenant, HasTenants
{
use CentralConnection; // identity always lives in the central database
use HasPackstubTenants; // canAccessTenant() + getTenants() + getDefaultTenant()
}
HasPackstubTenants implements all three contract methods and hides tenants that are still provisioning, so nobody lands in a half-built panel.
5. Register the plugin
In your panel provider (typically app/Providers/Filament/AdminPanelProvider.php):
use Packstub\Tenancy\TenancyPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->id('admin')
->path('admin')
->login()
->plugin(TenancyPlugin::make());
}
That one line wires the tenant model, tenant routing (full-host in subdomain mode, /teams/{tenant} in path mode), the tenant switcher, the onboarding wizard, and the provisioning status page.
Important
Do not add stancl's identification middleware (InitializeTenancyByDomain, InitializeTenancyByPath, PreventAccessFromUnwantedDomains) to the panel. The plugin handles identification itself; adding them breaks the central login page. Why →
6. Create the tenant migrations directory
Every migration in database/migrations/tenant/ runs against each new tenant database:
mkdir -p database/migrations/tenant
Put the tables your tenants own here — no tenant_id columns needed, isolation happens at the connection level. Keep users and sessions in the central database/migrations; identity and auth stay central by design (Isolation model).
7. Set your environment
APP_URL=http://myapp.test
QUEUE_CONNECTION=database
APP_URL matters: the central domain is parsed from it, and every tenant's subdomain (acme.myapp.test) is minted from the central domain when the tenant is created. Set it before creating your first tenant. (Created tenants against the wrong URL? php artisan tenants:domains --fix repairs them.)
No SESSION_DOMAIN needed — the plugin sets the session cookie domain per host at runtime.
8. Start a queue worker
php artisan queue:work
Provisioning (create database → migrate → seed → mark ready) runs as queued jobs. Without a worker, new tenants stay on the provisioning screen.
Next: create your first tenant
You're installed. Head to the Quickstart to create a user, walk through the onboarding wizard, and land in your first tenant's panel — or, if you'd rather do it from Tinker:
$tenant = app(Packstub\Tenancy\Services\TenantOnboarder::class)
->create(name: 'Acme Inc.', slug: 'acme', owner: App\Models\User::first());
$tenant->refresh()->status; // "provisioning" → "ready" once the worker finishes
Then visit http://acme.myapp.test/admin (subdomain mode) or http://myapp.test/admin/teams/acme (path mode).
Something not working? See Troubleshooting or email support@packstub.dev.
stancl/tenancy v4 stability
Filament Tenancy is built on stancl/tenancy v4. Upstream develops v4 on its master branch and hasn't tagged a release yet, so Packagist alone can't satisfy a default (stable) Composer setup.
You don't need to work around that. The Packstub registry serves a vetted v4 snapshot as a regular stable release — a date-stamped version of the form 4.0.0.<YYYYMMDD>, where the date is the mirrored upstream commit's — and the plugin requires ^4.0. A plain composer require resolves with Composer's default stability settings: no minimum-stability changes, no commit pins, nothing extra in composer.json.
- Auditable. The exact tree behind each version is tagged
mirror/<version>in the public provenance repository packstub/stancl-tenancy-mirror. Each plugin release's changelog states the snapshot it was tested against. - Stable by construction. Composer treats custom repositories as canonical for the packages they serve, so your installs resolve stancl/tenancy exclusively from the registry. An upstream push can never change what you deploy — new snapshots only arrive when you run
composer updateafter we publish one. - Temporary. Once stancl/tenancy tags a stable 4.0 release, the registry steps aside and stancl/tenancy resolves from Packagist again — a routine
composer update, no changes on your side.