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

Multitenancy Laravel Package

romegadigital/multitenancy

A simple multi-tenancy package for Laravel that scopes Eloquent models to the current tenant using a tenant_id column and global scopes. Includes middleware for tenant resolution, a Tenant model, and tenant-aware helpers for isolating data in a single database.

Deep Wiki
Context7

Getting Started

The package is archived (last release May 2023, effectively frozen at Laravel 9/10-era support), so treat it as a reference implementation rather than a long-term dependency. Pin your Laravel version before installing and expect to vendor-patch anything that breaks.

  1. Install and publish config
    composer require romegadigital/multitenancy
    php artisan vendor:publish --tag=multitenancy-config   # config/multitenancy.php
    php artisan vendor:publish --tag=multitenancy-migrations
    php artisan migrate                                     # landlord DB: `tenants` table
    
  2. Configure the base domain — set multitenancy.domain (or the APP_DOMAIN/MULTITENANCY_DOMAIN env var the config reads) so the package can strip the subdomain from Host. Everything hinges on this being correct.
  3. Register the middleware — add the package's tenant middleware to the web group (or a dedicated route group, which is better) in app/Http/Kernel.php. Order matters: it must run after StartSession and before anything that touches the tenant DB.
  4. Create your first tenant
    php artisan tenant:make acme --name="Acme Corp"
    
    This creates the Tenant record, provisions the database (default naming pattern lives in config), and runs your migrations against it.
  5. Hit the subdomain — browse http://acme.your-app.test. If you get the landlord app, the middleware isn't resolving; if you get a Connection refused, the tenant DB wasn't created.

First useful check: in a tinker session on a tenant subdomain, tenancy()->tenant (or Tenant::current()) should return the model; DB::connection()->getDatabaseName() should be the tenant DB.

Implementation Patterns

The core mental model. Two databases coexist: a landlord connection holding tenants (and anything global — plans, users directory, API keys) and a tenant connection that the middleware swaps at request start. The Tenant model stores the subdomain, DB name, and connection name; the package sets Tenant::current() and reconfigures database.connections.<tenant> at runtime.

Route segregation. Keep landlord routes (marketing, signup, billing) and tenant routes in separate files and apply the tenant middleware only to the tenant group. This avoids the "how do I know whether a tenant exists before routing?" problem entirely.

// routes/tenant.php
Route::middleware(['web', 'tenant'])->group(function () {
    Route::get('/', DashboardController::class);
});

// routes/web.php  — landlord only
Route::get('/', LandingController::class);

Resolving the tenant outside HTTP. For jobs, commands, and tests, the middleware isn't there. Either call the package's tenant-switching API explicitly or wrap work in a helper:

$tenant = Tenant::where('subdomain', 'acme')->first();
$tenant->makeCurrent();          // or tenancy()->initialize($tenant)
try {
    DoWork::dispatchSync();
} finally {
    tenancy()->forgetTenant();   // or $tenant->forget()
}

Commands for the tenant lifecycle. Learn the tenant:* artisan family — make, migrate, seed, list, delete — and use them in CI/deploy scripts. For per-tenant schema changes, run php artisan tenant:migrate after any migration commit; relying on the landlord migrate alone will silently leave tenants stale.

Tenant-scoped models. Models living in the tenant DB need a $connection set (the package usually exposes a base model or trait for this) and must never reference landlord tables in joins — cross-database joins are where this package's opacity bites hardest. Keep relations within one database; use IDs + service calls across the boundary.

Testing. Use a per-test tenant in setUp(): provision a tenant against SQLite-in-memory or a throwaway schema, call makeCurrent(), run the test, then forgetTenant(). Without the teardown the static current-tenant leaks between tests and you'll get flaky, order-dependent failures.

Extension points worth knowing. Config's tenant_model (swap for your own subclass with billing columns), the Tenant contract (if you implement your own resolver), and the middleware itself (subclass it to add reserved-subdomain checks, www redirects, or trial-expiry gates before the connection swap).

Gotchas and Tips

  • Archived ≠ stable. No security fixes, no Laravel 11/12 support, and package middleware may conflict with newer Laravel bootstrap changes. Vendor the relevant classes if you adopt it seriously, or port the pattern into first-party code.
  • config:cache will bite you. The domain value must come from config, and cached config makes env() calls return null. Run php artisan config:clear when subdomain resolution suddenly stops working.
  • Reserved subdomains (www, api, admin, mail) will try to resolve as tenants and 404 or, worse, match an existing tenant. Guard against this in a middleware subclass or by validating at tenant creation.
  • Connection purging. Switching tenants in the same process (queues, Octane, long-running workers) leaves stale PDO handles. Call DB::purge($connection) on switch/forget; otherwise a worker picks up the previous tenant's connection and you get cross-tenant data leaks. This is the single most dangerous failure mode.
  • Queue workers don't carry tenant context. Every job needs an explicit tenant ID and an initialize/forget wrapper, or dispatch on a tenant-tagged queue. The package does not do this for you.
  • Database creation privileges. tenant:make needs CREATE DATABASE rights on the MySQL user. On managed hosts (RDS, PlanetScale, Heroku) this often fails — pre-provision databases and register tenants manually.
  • Cache, sessions, and filesystems are not tenant-scoped by default. Add a tenant prefix to cache keys and session cookies (SESSION_COOKIE per subdomain) and a tenant subdirectory to storage paths, or tenants will share state.
  • Debugging order. (1) Does Host match a tenants.subdomain row? (2) Is the connection name in config/database.php set? (3) Is tenancy()->tenant non-null in the controller? A php artisan tinker one-liner per subdomain beats reading logs.
  • Deleting tenants is destructive and manual. There's no rollback safety net; make sure tenant:delete is gated and backed by a mysqldump (or snapshot) in production.
  • Custom tenant model gotcha. If you swap tenant_model, also re-publish the migration — the default migration's foreign keys and indexes expect the shipped schema, and a half-migrated landlord DB produces confusing "table not found" errors.
  • Migrate/seed per tenant is O(n). For a hundred tenants, deploy times grow linearly. Batch it in parallel or track a migrations watermark per tenant and only run for laggards.
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.
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
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect