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

Framework Laravel Package

tenancy/framework

Tenancy for Laravel provides a robust multi-tenant foundation: automatic tenant identification by domain or subdomain, database and connection switching, tenant-aware queues, caching, filesystem, and lifecycle events—with clean APIs for building SaaS applications and isolating tenant data.

View on GitHub
Deep Wiki
Context7

Getting Started

tenancy/framework is the foundation layer that tenancy/tenancy builds on. It ships the contracts, the Environment singleton, the bootstrapper pipeline and the lifecycle events — but almost never anything you'd call directly in application code. If you plan to use multi-tenancy day-to-day, you'll normally install tenancy/tenancy (or one of the tenancy/*-driver packages) and treat this package as a transitive dependency.

Install it directly only if you're building a custom multi-tenancy layer on top of the contracts:

composer require tenancy/framework

First places to look:

  1. Tenancy\Contracts\Tenant — the interface every tenant model must satisfy (getTenantKey, getInternalKey, and the metadata accessors).
  2. Tenancy\Environment — the singleton that holds "who is the current tenant" and drives bootstrap/revert.
  3. Tenancy\Contracts\Bootstrapper — the two-method contract (bootstrap(Tenant $tenant), revert(Tenant $tenant)) that every side-effectful subsystem (DB, cache, filesystem, queue, redis, session) implements.
  4. Tenancy\Events\* — lifecycle events fired around boot and revert; hook into these rather than overriding bootstrapper methods.
  5. config/tenancy.php — where the bootstrapper list lives and where you register your own.

A first working use case: define a Tenant model (implementing the contract), then in a controller call app(Environment::class)->tenant($tenant). That single call walks the configured bootstrapper list, fires Bootstrapping/Bootstrapped, and re-points database connections, cache prefixes, filesystem roots and queue connections to that tenant. Reverting is ->forgetTenant() (or ->tenant(null)), which runs the inverse pipeline and fires the revert events.

Implementation Patterns

Wrap every activation in try/finally. Because the environment is stateful and shared, the dominant pattern is:

$environment = app(Environment::class);
$environment->tenant($tenant);
try {
    // do tenant-scoped work
} finally {
    $environment->forgetTenant();
}

This mirrors the pattern used by the framework's own middleware and prevents a leaked tenant from bleeding into the next request or into a queue worker's next job.

Custom bootstrappers, not custom logic in controllers. When you need to switch something the framework doesn't cover (a search index alias, a feature flag prefix, a mailer config), implement Bootstrapper and register it in config/tenancy.php under bootstrappers. Keep bootstrap() and revert() strictly symmetric — anything you mutate in the first must be restored in the second.

Order matters. Bootstrappers run top-to-bottom on bootstrap() and bottom-to-top on revert() (or at least, reverting follows the reverse registration order in the default pipeline). Database switching in particular must run before anything that opens a connection — cache stores that live on the DB, queues, sessions. Put your custom bootstrapper after the built-ins unless you can prove it's independent.

Event-driven integration. Subscribe to Tenancy\Events\Bootstrapped to run tenant-specific setup (seed feature flags, warm caches), and to Reverted to tear it down. This keeps your code decoupled from the bootstrapper list and lets you compose behaviour without touching config.

Custom tenant classes. Extend Tenancy\Abstracts\Tenant (or implement the contract from scratch) when you need extra attributes — billing plan, region, branding — and resolve the current tenant from your own middleware (domain, subdomain, header) that then hands the instance to Environment::tenant().

Testing pattern. In tests, boot a tenant in setUp() and forget it in tearDown(), or use the framework's DatabaseTransactions-style helpers. Prefer asserting via events (e.g. Event::assertDispatched(Bootstrapped::class)) rather than poking at connection state directly.

Gotchas and Tips

  • The environment is a singleton and is not auto-reset between requests in the same process (queue workers, Octane). You must call forgetTenant() explicitly — Laravel's normal container reset won't help you here.
  • Reverting is not exception-safe by default. If your code throws between tenant($t) and forgetTenant(), you're left in a booted state. Always use try/finally, or wrap activations in a middleware that reverts in a terminate()/finally block.
  • Config cache can eat your bootstrapper list. If you add a custom bootstrapper and it "disappears" in production, run php artisan config:clear — the list is read from config/tenancy.php and is cached.
  • Bootstrapper order is a footgun. Cache, session and queue bootstrappers assume the database bootstrapper ran first when their store is DB-backed. Reordering the config array for a custom need can silently break those.
  • The package is effectively archived. The tenancy ecosystem hasn't seen active development for years; pin to a known-good version in composer.json and be prepared to fork. Expect stale dependencies (Laravel 8/9-era) and no community patches.
  • Domain/hostname identification doesn't live here. It's in the tenancy/*-identification packages — if you're looking for "which tenant does this request belong to?", you're in the wrong repo. This package only answers "put the system into tenant X's state."
  • Extension points to prefer when possible: the Extendable/hooks trait on bootstrappers for minor tweaks, event listeners for side effects, and full custom bootstrappers for anything with a matching revert. Overriding Environment itself is almost always the wrong move.
  • Debugging tip: when a tenant isn't behaving, dump app(Environment::class)->getTenant() at the point of failure. Half the "wrong database" bugs are a forgotten forgetTenant() from a prior request leaking in a long-running worker, not a bootstrapper bug.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
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