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 Tenancy Bundle Laravel Package

hakam/multi-tenancy-bundle

Hakam Multi-Tenancy Bundle is a Laravel package for adding multi-tenant support with tenant-aware database connections, migrations, and configuration, helping isolate tenant data and simplify managing multiple tenants in one application.

View on GitHub
Deep Wiki
Context7

Getting Started

First, reset expectations: this is a Symfony bundle, not a Laravel package. There is no service provider, no config/app.php entry, no Eloquent. Map your Laravel instincts accordingly:

Laravel habit Symfony equivalent here
Middleware resolving tenant Kernel event listener on kernel.request
Eloquent global scope Doctrine SQLFilter / entity listener
config/tenancy.php config/packages/*.yaml + bundle Extension
Service provider bindings services.yaml + compiler passes

Minimal path to a working tenant-aware app:

  1. composer require hakam/multi-tenancy-bundle — v3.1.0 requires PHP 8.1+. If you are still on 8.0, you are pinned to the v3.0.0 line and get no fixes at all, so check platform.php in composer.json before you start.
  2. Register the bundle in config/bundles.php:
    Hakam\MultiTenancyBundle\HakamMultiTenancyBundle::class => ['all' => true],
    
  3. Add a config block under config/packages/. Multi-tenancy bundles generally need three decisions up front: how a tenant is identified (domain, subdomain, header, or path), where tenant metadata lives (a tenant table or a static map), and how data is isolated (shared schema with a tenant_id, or database-per-tenant).
  4. Implement whatever tenant entity/interface the bundle expects, then point the resolver at your identification strategy.
  5. First use case to prove the wiring: subdomain resolution on a shared schema. acme.example.com resolves tenant acme, Doctrine filters every query by tenant_id, and a debug endpoint dumps the resolved tenant. This exercises resolution, context propagation, and query scoping in one pass — the three things that break independently later.

Where to look first: the bundle's DependencyInjection/ directory (what config keys actually exist), then its event listeners (resolution order), then its Doctrine integration. With 133 stars and zero dependents, the source is the documentation for edge cases. v3.1.0 exists mostly to repair a broken documentation link, so treat the in-repo source as more reliable than the rendered docs regardless.

Implementation Patterns

Resolution at the edge, propagation in the middle. Resolve the tenant once in a kernel.request listener and store it in a request-scoped context service. Everything downstream — repositories, mailers, file storage, cache — reads from that service rather than re-parsing the host. Never let controllers resolve tenants themselves; you will end up with two code paths that disagree.

Doctrine filtering for shared-schema tenancy. Enable the bundle's filter and make sure it is active for every entity manager you use, not just the default one. Wrap writes in an entity listener that stamps tenant_id on persist so a forgotten assignment cannot create an orphan row. Treat the filter as a safety net, not a substitute for explicit tenant_id in queries — filters can be disabled at runtime and silently widen a result set.

Database-per-tenant switching. If you go this route, the tenant context must mutate the connection parameters before the first query, and you must clear() the entity manager when switching. Long-running processes (Messenger workers, Swoole/RoadRunner) are where this pattern earns its complexity — and where it bites hardest.

Console and async are separate worlds. There is no request in a command, so no resolver runs. Every console command that touches tenant data needs an explicit --tenant= option that pushes the context manually. For Messenger, stamp the tenant identifier into the message envelope (a custom Stamp) and restore context in a middleware — otherwise a worker will happily process tenant A's job against tenant B's connection.

Testing. Functional tests should set the Host header rather than mocking the context service; that validates the resolver, which is the part most likely to be misconfigured. For shared-schema, load fixtures inside an explicit tenant context and assert that a query for tenant A returns zero rows from tenant B — make that a standing regression test.

Integration tips. Namespace cache keys, session cookie domains, uploaded-file paths, and mailer sender addresses by tenant — the bundle will not do this for you, and cross-tenant cache bleed is the classic post-launch incident. Subdomain tenancy also means cookies need domain: .example.com with tenant-scoped session names, or users will share sessions across tenants.

Upgrade cadence. v3.x is a stable API as of 3.1.0 — the 3.0.0 → 3.1.0 delta is a docs link fix plus the PHP 8.1 floor, no signature or config changes. That means a minor bump is safe to take, but do not read a quiet changelog as "mature": there are still zero dependents, so regressions land on you first.

Gotchas and Tips

  • Ecosystem risk is real. Zero dependents and a small star count mean you are an early adopter. Pin the version in composer.json, read the changelog before every bump, and be prepared to fork. The listed release date (2026-04-26) is ahead of the current date — verify what you actually install against Packagist before trusting the metadata.
  • PHP version is a hard gate. v3.1.0 dropped the pre-8.1 path. On shared hosting or an inherited container image still running 8.0, composer update will quietly resolve you back to v3.0.0 and you will spend an afternoon wondering why the docs link 404s.
  • CLI has no tenant. The single most common bug report in every tenancy bundle: migrations run, seeders run, cron runs — all without a tenant. Decide explicitly whether "no tenant" means fail loudly or all tenants. Default to failing loudly.
  • Doctrine identity map leaks. In a worker or a test suite, entities from the previous tenant remain managed. $em->clear() on every context switch, or you will serve a stale object across tenants.
  • Cached metadata and container config are global. Symfony's cache is shared across tenants by default. Per-tenant cache pools, per-tenant translation domains, and per-tenant Twig paths all need explicit wiring.
  • Migrations multiply. Database-per-tenant means N migration runs; script it (for each tenant: doctrine:migrations:migrate --em=tenant) and make it idempotent, or you will drift.
  • Debugging resolution order. When the wrong tenant appears, bin/console debug:event-dispatcher kernel.request shows listener priority. A security listener firing before tenant resolution is the usual culprit.
  • Extension points to reach for first: a custom tenant resolver (implement the bundle's resolver interface and tag the service) and a custom tenant provider if your metadata lives somewhere unusual. Both are cheaper than patching the bundle.
  • Laravel-dev trap: do not look for a facade or a Tenant::current() global. Inject the context service. Static tenant state is exactly what makes this class of package painful in tests.
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.
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
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