Packstub.

Troubleshooting

This page lists the problems Packstub Tenancy users hit most often, organized by symptom. Each entry explains what you are seeing, why it happens, and how to fix it. Before digging in, make sure you completed every step in the installation guide — most issues below are wiring mistakes that the install notes call out. If your problem involves the database pool, also see Horizontal scaling.

Tenants are stuck on the provisioning screen

Symptom: After registering a tenant, the user sits on the "setting up" page indefinitely. After about 90 seconds the page shows a "taking longer than usual" hint, and after 300 seconds it gives up and shows the failed state.

Cause: Provisioning (CreateDatabaseMigrateDatabase → optional SeedDatabaseMarkTenantReady) runs as a queued job pipeline, dispatched after the onboarding transaction commits. If no queue worker is running, the job sits in the queue forever and the tenant never leaves the provisioning status. The provisioning page polls every 2 seconds and hard-times-out at 300 seconds as a safety net — a timeout here almost always means the worker is down or wedged.

Fix: Run a queue worker in production:

php artisan queue:work

(or supervise it with Horizon / systemd / Supervisor). Once the worker drains the queue, the polling page redirects the user into their panel automatically — the 300-second timeout only affects the page, not the queued job.

If the job actually failed (exhausted its retries), the tenant's status flips to failed and the page shows the failure state immediately. Fix the underlying cause (check storage/logs/laravel.log and your failed_jobs table), then re-run the pipeline:

php artisan tenants:retry-provisioning acme

The retry command only accepts tenants in the failed status. Retrying is idempotent: an already-created tenant database is reused, and migrations re-run safely.

Users are bounced to /login after every save (subdomain mode)

Symptom: Login works and pages render, but every Livewire interaction — saving a form, running a table action — redirects the user back to the panel login page.

Cause: The user's session lives in the tenant database — Stancl's DatabaseSessionBootstrapper is active. Sessions and auth are central in this plugin's architecture (see Isolation model); with per-tenant sessions, StartSession looks in the wrong database on any request where tenancy isn't initialized first (the login page, the global /livewire/update route, …), finds no session, and Filament treats the user as logged out. Note that stancl's raw published config enables this bootstrapper by default — you don't have to have opted in for it to be active. Current plugin versions refuse to boot while it is enabled (a RuntimeException naming the fix), and the installer publishes config/tenancy.php with it disabled, so this symptom mostly appears on installs that predate the guard. A closely related symptom on such installs is a hard 500 — no such table: sessions on the tenant connection — on every tenant page.

Fix: Remove DatabaseSessionBootstrapper from tenancy.bootstrappers (and drop any sessions table from database/migrations/tenant/). If earlier docs led you to add InitializeTenancyByDomain / PreventAccessFromUnwantedDomains to the panel middleware, remove those too — the plugin's own global identification middleware replaces them (see below).

500 on the central login / 404 on every tenant page (subdomain mode)

Symptom: The central domain throws TenantCouldNotBeIdentifiedOnDomainException on /admin/login, or every route on a tenant subdomain 404s.

Cause: Stancl's InitializeTenancyByDomain and/or PreventAccessFromUnwantedDomains were added to the panel middleware. InitializeTenancyByDomain demands a tenant on every host — including your central domain (its skip logic only applies in the global stack) — and PreventAccessFromUnwantedDomains 404s unflagged routes on non-central hosts.

Fix: Remove all stancl identification middleware from the panel. Subdomain identification is handled by the plugin's own IdentifyTenantHost middleware, registered globally and aware of your central_domain — there is nothing to wire.

RouteIsMissingTenantParameterException (path mode)

Symptom: Requests throw Stancl\Tenancy\Exceptions\RouteIsMissingTenantParameterException, typically on /login or another central route.

Cause: Stancl\Tenancy\Middleware\InitializeTenancyByPath was added to the panel middleware. That middleware demands a {tenant} parameter on every route it runs on, and central panel routes such as /login have none. Path mode needs no Stancl identification middleware at all: Filament resolves the tenant from the URL itself, and the plugin bridges the resolved tenant into stancl/tenancy automatically (Filament's TenantSet event → the plugin's SyncStanclTenancy listener).

Fix: Remove InitializeTenancyByPath (and any other Stancl identification middleware) from your panel. In path mode the plugin's bridge is the identification mechanism.

404 on tenant routes (path mode)

Symptom: In path mode, links into the tenant area — or redirects after login — produce 404s, often with the tenant's ID in the URL where the slug should be.

Cause: The plugin registers Filament's tenant route binding on the slug attribute, so panel URLs look like /admin/teams/acme. Stancl's path machinery (the PathTenantResolver, and the UrlGeneratorBootstrapper's URL defaults applied through the TenancyUrlGenerator override) fills the {tenant} parameter from its tenant_model_column setting — which defaults to the tenant's primary key. With the default in place, Stancl-generated URLs carry the tenant ID, and Filament's slug binding cannot resolve them.

Fix: Point the resolver at the slug in config/tenancy.php:

'identification' => [
    'resolvers' => [
        Stancl\Tenancy\Resolvers\PathTenantResolver::class => [
            // ...
            'tenant_model_column' => 'slug',
        ],
    ],
],

Boot exception: "packstub/filament-tenancy requires tenancy.database.central_connection"

Symptom: The application throws at boot:

packstub/filament-tenancy requires tenancy.database.central_connection to be set to your
central database connection name.

Cause: tenancy.database.central_connection is null or empty. The plugin pins all central writes — the tenant model, onboarding, profile updates, resource-syncing masters — to this connection. Without it, those writes would silently fall through to whatever connection is active, which inside a tenant context is the tenant database. The plugin refuses to boot rather than risk corrupting data.

Fix: Set the central connection in config/tenancy.php:

'database' => [
    'central_connection' => env('DB_CONNECTION', 'central'),
    // ...
],

Make sure DB_CONNECTION is set in your environment, or hardcode your central connection's name.

Provisioning fails with an SQLSTATE error on CREATE DATABASE

Symptom: Tenants land in the failed status, and the failed job's exception is an SQLSTATE error from a CREATE DATABASE statement (permission denied, unknown database, connection refused).

Cause: A pool member (or your template tenant connection) cannot create databases. Each connection used for tenant databases must define the server's host and port, admin credentials with the create-database privilege, and an existing maintenance database to connect to — provisioning connects to that database first, then issues CREATE DATABASE from there.

Fix: Probe every pool member:

php artisan tenants:pool --check

The check connects to each member and verifies it can create tenant databases — on PostgreSQL it confirms the role has CREATEDB (or superuser), on MySQL/MariaDB a global CREATE grant. Grant the missing privilege:

-- PostgreSQL
ALTER ROLE tenants_a CREATEDB;

-- MySQL / MariaDB
GRANT CREATE ON *.* TO 'tenants_a'@'%';

Also confirm the database field of the connection names a database that already exists on that server. Then retry the affected tenants with php artisan tenants:retry-provisioning {slug}.

tenants:pool exits 1 and reports stranded tenants

Symptom: php artisan tenants:pool exits with code 1 and prints:

2 tenant(s) point at connections missing from config/database.php — they cannot boot:
  acme -> tenant_pool_b
  globex -> tenant_pool_b

Cause: A connection that tenants were assigned to was renamed or removed from config/database.php. Each tenant stores its server as a persisted tenancy_db_connection attribute; if that name no longer resolves to a connection, the tenant cannot boot — provisioning, runtime access, and deletion all fail.

Fix: Either restore the connection definition under its original name, or repoint each stranded tenant at an existing member:

$tenant = \Packstub\Tenancy\Models\Tenant::where('slug', 'acme')->first();
$tenant->setInternal('db_connection', 'tenant_pool_a');
$tenant->save();

Only repoint a tenant at a server that actually holds its database. If the data lives elsewhere, move it first (dump and restore the tenant database onto the target server), then update the attribute — there is no automatic tenant-move primitive. Treat renaming pool connections as a breaking change.

Queue workers throw DatabaseManagerNotRegisteredException (or a TypeError about the connection config)

Symptom: Provisioning jobs fail on your queue workers with Stancl\Tenancy\Database\Exceptions\DatabaseManagerNotRegisteredException, or with a TypeError complaining that the template connection config is null — while the same operations work on the web servers.

Cause: Provisioning runs on the queue, so the worker process resolves the tenant's server from its tenancy_db_connection attribute — against the worker's own configuration. If the worker environment is missing the pool connection definitions (unset environment variables, an out-of-date deployment, a stale config cache), the lookup returns nothing: a missing connection surfaces as the null-config TypeError, and a connection whose driver has no entry in tenancy.database.managers throws DatabaseManagerNotRegisteredException.

Fix: Deploy identical config/database.php connections (and their environment variables) everywhere workers run, then clear the config cache and restart the workers so they pick up the new configuration:

php artisan config:clear
php artisan queue:restart

A tenant sees wrong or empty data after the pool was enabled

Symptom: You enabled the database pool on an app with existing tenants. New tenants work, but a pre-existing tenant now sees empty tables or another dataset entirely.

Cause: The pool assigns a server only to new tenants, at creation time. Existing tenants have no persisted tenancy_db_connection and keep following Stancl's tenancy.database.template_tenant_connection (or the central connection) — tenants:pool shows them as the (template default) row. If you repointed that template connection at a pool server when enabling the pool, those tenants now connect to a server that does not hold their databases: Stancl connects to whatever database matches the tenant's database name there, which is either missing or freshly empty.

Fix: Leave the template connection pointing at the server where the existing tenant databases actually live; the pool balances new tenants independently. To move an existing tenant onto a pool member, dump and restore its database to the target server, then pin it explicitly:

$tenant->setInternal('db_connection', 'tenant_pool_b');
$tenant->save();

Explicitly pinned tenants are never reassigned by the pool.

Every provision fails at the seed step

Symptom: Every tenant registration fails, and the failed job's stack trace points at a seeder — typically referencing tables or models that only exist in the central database.

Cause: Tenant seeding ran with a seeder that is not tenant-safe. Your application's Database\Seeders\DatabaseSeeder seeds the central schema; run against a fresh tenant database it fails — or worse, silently misbehaves. This is why tenant seeding is opt-in: with packstub-tenancy.seeder left null (the default), the pipeline skips the seed step entirely.

Fix: Write a seeder that only touches tenant-schema tables and opt in to it:

// config/packstub-tenancy.php
'seeder' => Database\Seeders\TenantSeeder::class,

The plugin runs it with --force, so non-interactive queue workers never prompt. If you don't need seeded data, set 'seeder' => null and retry the failed tenants.

RuntimeException: queued resource syncing needs the QueueTenancyBootstrapper

Symptom: The application throws at boot:

Resource syncing is configured to queue (queueResourceSync), but
Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper is not enabled in
tenancy.bootstrappers.

Cause: You called ->queueResourceSync(), but Stancl's QueueTenancyBootstrapper is not in tenancy.bootstrappers. That bootstrapper is what restores the originating tenant (or central) database context inside a queue worker. Without it, a queued sync job runs against whatever connection the worker happens to have — silently reading and writing the wrong database — so the plugin fails loudly instead.

Fix: Enable the bootstrapper in config/tenancy.php:

'bootstrappers' => [
    // ...
    Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper::class,
],

or run the sync listeners inline with ->queueResourceSync(false).

Resources error about a missing tenant_id column

Symptom: Opening a Filament resource inside a tenant throws an SQL error such as SQLSTATE[42703]: Undefined column: column "tenant_id" does not exist.

Cause: scope_resources_to_tenant is true (or you called ->scopeResourcesToTenant()). That setting makes Filament add a tenant-ownership constraint to every resource query — which expects a tenant_id column on your tables. In multi-database tenancy no such column exists: each tenant's tables live in their own database, and Stancl's DatabaseTenancyBootstrapper already isolates tenants at the connection level.

Fix: Keep the setting at its default:

// config/packstub-tenancy.php
'scope_resources_to_tenant' => false,

Only enable it for single-database setups where tenant rows share tables and genuinely carry a tenant_id discriminator column.

Still stuck?

Set support_url in config/packstub-tenancy.php so your own users land on your support channel when provisioning fails. For issues with the plugin itself, email support@packstub.dev — bug reports from license holders are answered directly, and fixes ship as regular releases.