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

Multi Tenant Laravel Package

laraflock/multi-tenant

A Laravel package for building multi-tenant applications with tenant-aware database connections, migrations, and model scoping. It helps isolate tenant data and switch between tenants easily, supporting both single-database and multi-database setups.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install with composer require stancl/tenancy:^5.9.1. Release 5.9.1 re-adds Laravel 9 support to the 5.x line, so Laravel 9 applications should pin ^5.9.1 (or 5.9.*) rather than assuming 5.9.0 resolves cleanly.
  • Run the installer and inspect what it publishes before editing anything: php artisan tenancy:install. It creates config/tenancy.php, config/tenancy.php-referenced migrations, routes/tenant.php, and registers the service provider.
  • Migrate central tables (php artisan migrate) — this creates tenants and domains — then create your first tenant:
$tenant = Tenant::create(['id' => 'acme']);
$tenant->domains()->create(['domain' => 'acme.localhost']);
  • Check config/tenancy.php first when something is wrong. The keys that matter most on day one: tenant_model, id_generator, domain_model, central_domains, bootstrappers, database, cache, filesystem, redis, queues.
  • First real use case: domain-based identification with one database per tenant. Add InitializeTenancyByDomain and PreventAccessFromCentralDomains to the middleware group wrapping routes/tenant.php, set central_domains to your app/central hostnames, then hit the tenant domain and confirm tenancy()->tenant is populated.

Implementation Patterns

  • Route separation. Keep routes/web.php central-only and routes/tenant.php behind the tenancy middleware group. Anything that must work on both sides (health checks, asset routes) belongs in central routes or a separate unauthenticated group.
  • Manual lifecycle control for console code, tests, and edge cases:
tenancy()->initialize($tenant);
// ... tenant-scoped work
tenancy()->end();

// or scoped
$tenant->run(fn () => User::count());

Always pair initialize() with an end()/try-finally; leaking tenant context into the next request or loop iteration is the most common source of cross-tenant data bugs.

  • Queues. Implement TenantAware on jobs (or use $tenant->run() inside the handle), and keep the queue bootstrapper enabled so the tenant is re-initialized when the worker picks the job up. Ensure the tenant's DB connection details are serialized in the payload, not resolved from central config.
  • Caching. Enable CacheTenancyBootstrapper and use a cache store that supports tags (Redis/Memcached) or rely on the automatic prefixing. Without it, Cache::get('settings') returns one tenant's data to another.
  • Filesystem. FilesystemTenancyBootstrapper rewrites storage_path()/disk roots per tenant with a suffix; reference storage_path() and Storage::disk() rather than hardcoding storage/app.
  • Commands. Use tenancy:run for ad-hoc per-tenant commands, or loop tenants explicitly with tenants()->each(fn ($t) => $t->run(...)) when you need per-tenant error handling and logging.
  • Events as extension points. Hook TenantCreated, TenancyInitialized, TenancyEnded, and Bootstrapping/Bootstrapped for seeding, provisioning, and per-tenant config loading.
  • Testing. Initialize a tenant in setUp() (or use the package's testing helpers), run tenant migrations against the tenant connection, and assert isolation explicitly — e.g. create a row for tenant A, switch to B, assert it is absent.

Gotchas and Tips

  • Version pinning on Laravel 9. 5.9.1 is a compatibility patch, not a feature release. If CI previously failed to install on Laravel 9, verify the resolved package version via composer show stancl/tenancy — a stale composer.lock or an overly tight constraint elsewhere can still hold you on 5.9.0.
  • central_domains is load-bearing. If it is empty or wrong, InitializeTenancyByDomain will either not match or your central app will be treated as a tenant domain. Keep it env-driven (CENTRAL_DOMAINS) and in sync across environments.
  • Config caching. php artisan config:cache is fine, but tenancy.php values are read at boot; anything that mutates tenant config at runtime (custom bootstrappers, per-tenant DB overrides) must run after TenancyInitialized, not during config load.
  • Bootstrapper order matters. Bootstrappers in config/tenancy.php run in array order. Database must be initialized before anything that queries tenant data (cache, filesystem, queue). When adding a custom bootstrapper, register it explicitly and make it idempotent.
  • Tenant IDs are used as DB names and path segments. Constrain the id_generator (or validate input) — hyphens/spaces/uppercase cause failures on MySQL database names and on case-sensitive filesystems. Prefer UUIDs or slug-safe IDs.
  • Redis needs prefixing. Enable the Redis bootstrapper and confirm Redis::connection() is re-resolved after tenancy init; connection objects cached by the container before initialization will silently share a namespace across tenants.
  • Debugging checklist: dump tenancy()->tenant and tenancy()->initialized; confirm the middleware is actually on the route (php artisan route:list -v); check that the tenant DB exists and database.managers config points at the right connection; tail logs for the domain-identification middleware.
  • Central-only singletons. Bind tenant-dependent services with scoped() rather than singleton(); a singleton resolved on the first request will pin the first tenant's connection for the worker's lifetime.
  • Extension points worth knowing: custom DomainTenantResolver/identification middleware, custom TenantContract implementations, custom bootstrappers, tenancy:run for scripted operations, and the TenantAware job contract for queue isolation.
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.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings