spatie/laravel-multitenancy
An unopinionated Laravel package that makes your app tenant aware. It determines the current tenant per request, handles switching tenants, and supports single or multi-database setups, plus tenant-aware queued jobs and artisan commands.
Architecture fit. spatie/laravel-multitenancy is deliberately unopinionated: it resolves and binds a "current tenant" into the IoC container (currentTenant) and mirrors it into Laravel's Context (tenantId), then fires lifecycle hooks when the tenant switches. It does not add global query scopes, automatic schema separation, or per-tenant migration orchestration. For a TPM, that means this is an enabling primitive, not a finished multitenancy platform. It fits well if we already have (or are willing to build) our own isolation layer: a TenantFinder (domain/subdomain/path/header → tenant), per-tenant database connections, tenant-scoped cache prefixes, and a migration runner. It fits poorly if the business expects "turn on multitenancy" with minimal application surgery.
Integration feasibility. Low-friction to install (Composer, MIT, Laravel 11/12 compatible in the 4.x line, PHP 8.2+), but the real effort is application-side and concentrated in four areas: (1) the tenant model and landlord/tenant data split, (2) connection switching on models, (3) queue worker behavior (tenant-aware vs. NotTenantAware jobs, retries, encrypted/ShouldBeEncrypted jobs, queued closures), and (4) cache/session/route-cache isolation — especially under Octane, where the package has had to add route-cache path switching and router reload work. The package's own test suite requires three MySQL databases (landlord, tenant_1, tenant_2), which is a good proxy for the CI matrix we will need to stand up.
Technical risk. Moderate. Highest-severity risks are silent cross-tenant leakage rather than package failure: because isolation is opt-in, a single model, raw query, cache call, or event listener that bypasses the tenant context can leak data. Secondary risks are queue-related correctness (tenant context must travel with serialized jobs; Fresh Tenant on each job, retry handling, and encrypted payload handling have all needed fixes historically), Octane/long-lived-process state bleed, and the historical churn around makeCurrent/isCurrent semantics (1.6.4–1.6.6) and container bindings (3.0.3 "Bind or forget current tenant"). Vendor risk is low: Spatie is a well-established maintainer with docs, changelog, releases, and paid support channels, though MIT means no SLA. The reported score (43.18/100) and "0 dependents" signal are worth validating directly against Packagist/GitHub before we treat adoption as low-risk — 1.3k stars with zero dependents is inconsistent and suggests the dependent-count metric is incomplete rather than that the package is unused.
Key questions.
tenant_id discriminator, database-per-tenant, or a hybrid (landlord DB + tenant DBs)? The package supports the last two best; the first requires us to build scoping ourselves.stancl/tenancy (batteries-included: automatic migrations, cached lookups, domain routing) a better fit for our timeline than assembling the pieces here?Stack fit. Strong for a standard Laravel app on MySQL/PostgreSQL with a landlord database plus per-tenant databases, using subdomain or domain-based resolution. The package's niceties map cleanly to a typical SaaS: TenantFinder for request resolution, TenantAware jobs for async work, tenants:artisan for per-tenant commands and migrations, cache prefix tasks, and TenantCollection helpers (filterCurrent, rejectCurrent) for admin tooling. It is a weaker fit for a single-database, shared-schema design, where the package adds context plumbing but we still write all the scoping.
Migration path. Treat this as a strangeblau migration, not a flag flip. Sequence: (1) introduce a landlord connection and a Tenant model; (2) make tenant identification additive (resolve tenant, bind context) without changing data access; (3) move tenant-owned tables to per-tenant databases behind feature-flagged model connections; (4) convert queues and scheduled commands; (5) decommission shared-table access paths. Existing single-tenant customers become "tenant 1," so the cutover can be piloted on one low-risk account before broad rollout. Expect a dual-read/dual-write window if data must be backfilled into tenant databases.
Compatibility. Pin to the 4.x line for Laravel 11/12 and PHP 8.2+; 3.x covers Laravel 9/10. The 4.0 breaking change (introducing the IsTenant contract) plus 4.0.6's configurable TenantAware/NotTenantAware interfaces mean our code should depend on the contract and config, not concrete classes, to survive minor upgrades. Verify compatibility with our Octane, Horizon, and queue-encryption settings explicitly; several recent patches (4.0.5 encrypted jobs, 2.3.9 cache repository, 2.3.1 router reload) touch exactly the integrations most likely to bite us. Note that the package's own test matrix needs multiple MySQL databases — mirror that in CI early, since our test suite will be materially slower and more complex post-adoption.
Sequencing. Week 0: timeboxed spike (2–3 engineer-days) proving tenant resolution, connection switching, and one tenant-aware job end-to-end, plus a written comparison against stancl/tenancy. Weeks 1–2: landlord/tenant schema split and provisioning script. Weeks 3–5: model connections, cache/session/storage prefixing, queue conversion. Weeks 6–7: per-tenant migrations via tenants:artisan, admin/onboarding tooling, observability. Week 8: pilot tenant, then staged rollout. Gate each phase on an explicit isolation test suite (attempt cross-tenant reads, cache collisions, job context bleed) rather than on feature completion.
Maintenance. The package itself is low-maintenance and actively released; our maintenance burden is in the surrounding isolation code, which we own entirely. Budget ongoing effort for: keeping the Tenant contract stable, reviewing every new model/query/cache call for tenant awareness, and re-testing after Laravel upgrades (the 3.x→4.x path shows breaking changes do occur, roughly annually). Vendor dependency is single-source (Spatie), MIT-licensed, with no contractual support — acceptable for a library of this size, but it should be recorded in the dependency risk register with an exit path (alternatives: stancl/tenancy, tenancy/tenancy).
Support. Community support via GitHub issues and Spatie's docs; no SLA. Internal runbooks must therefore cover the top failure scenarios ourselves. Assign a named owner (backend lead) for the tenancy subsystem and require ADR-level documentation of our isolation rules — this is the single highest-leverage operational control, because the package's minimalism pushes correctness into application conventions.
Scaling. Request-path cost is small (tenant lookup dominates; cache the lookup). Real scaling pressure is operational: database-per-tenant multiplies connection count, backup jobs, migration runtime, and monitoring surface linearly with tenant count. At hundreds of tenants this is routine; at thousands, provisioning automation and connection pooling become prerequisites. Octane improves throughput but raises state-bleed risk, so tenant reset must be verified under load. Queue throughput needs per-tenant fairness controls so one large tenant cannot starve others — the package provides context, not scheduling policy.
Failure modes. Watch for: (a) tenant context absent in a job, command, listener, or notification → writes to landlord or wrong tenant; mitigate with NotTenantAware on anything that must not inherit context (3.0.0 changed this to forget the current tenant) and fail-fast assertions; (b) Octane/worker state bleed between requests or jobs; (c) cache key collisions across tenants if prefixing is misconfigured; (d) unserialization failures for models requiring an active tenant (fixed in 4.0.2, but a signal that serialized payloads carry implicit tenant assumptions); (e) migration drift when tenants:artisan partially fails across a fleet, leaving tenants on mixed schema versions — needs idempotent migrations and a schema-version column; (f) tenant-not-found requests, now surfaced via TenantNotFoundForRequestEvent, which should feed alerting rather than 500s.
Ramp-up. Expect 3–5 engineer-days for a mid-level Laravel developer to become productive with the package's concepts (current tenant, finder, switch tasks, tenant-aware interfaces) and 2–3 weeks before the team is fluent in the failure modes above. Invest in a short internal doc plus the isolation test suite as onboarding artifacts. Stakeholder-facing ramp-up is mostly expectation-setting: this package gives us a foundation, and the visible multitenancy features (self-serve onboarding, per-tenant admin, data export/deletion) remain our build.
How can I help you explore Laravel packages today?