codeplace-io/multitenancy-bundle
Installation
composer require codeplace-io/multitenancy-bundle
Publish the bundle’s configuration and migrations:
php artisan vendor:publish --provider="Codeplace\MultitenancyBundle\MultitenancyServiceProvider" --tag="config"
php artisan vendor:publish --provider="Codeplace\MultitenancyBundle\MultitenancyServiceProvider" --tag="migrations"
Run migrations:
php artisan migrate
Configure Tenant Model
Extend the provided Tenant model (or create your own) in config/multitenancy.php:
'tenant_model' => \App\Models\Tenant::class,
First Tenant Request Use middleware to resolve the tenant before each request:
// app/Http/Kernel.php
protected $middlewareGroups = [
'web' => [
\Codeplace\MultitenancyBundle\Http\Middleware\ResolveTenant::class,
// ... other middleware
],
];
Test with a request to /tenant/{tenant-slug} (or your defined route).
Define tenant-specific routes in routes/web.php:
Route::middleware(['tenant'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
});
Use tenant() helper to access the current tenant:
$tenant = tenant(); // Returns the resolved Tenant model
tenant.example.com → config/multitenancy.php subdomain key)./tenant/{slug} → config/multitenancy.php route key).X-Tenant-ID → Custom middleware).config/multitenancy.php:
'fallback_tenant' => 1,
$users = User::all(); // Automatically scoped to tenant's DB
tenant()->unscoped() or DB::connection()->getDatabaseName() to bypass scoping.ResolveTenant to add logic:
public function handle($request, Closure $next) {
$tenant = Tenant::where('identifier', $request->header('X-Tenant-ID'))->first();
if (!$tenant) abort(404);
tenant()->set($tenant);
return $next($request);
}
Route::middleware(['tenant', 'auth:tenant'])->group(...);
Tenant factories to seed data per tenant:
// database/seeders/TenantSeeder.php
Tenant::factory()->create(['identifier' => 'acme']);
User::factory()->create(['tenant_id' => tenant()->id]);
return response()->json([
'data' => $data,
'tenant' => tenant()->toArray(),
]);
tenant() in API controllers to validate requests:
public function store(Request $request) {
$request->validate(['tenant_id' => 'required|exists:tenants,id']);
// ...
}
Database Connection Leaks
tenant()->resetConnection() in middleware or after tenant changes:
tenant()->set($newTenant)->resetConnection();
Caching Conflicts
Cache::put("tenant_{$tenant->id}_key", $value, $seconds);
Migration Conflicts
php artisan migrate --tenant=1 or scope migrations to a tenant.Middleware Order
ResolveTenant must run before Auth or other tenant-dependent middleware.web middleware group.\Codeplace\MultitenancyBundle\Facades\Tenant::set($tenant);
\Log::debug("Current tenant: ", tenant()->toArray());
\DB::connection()->getDatabaseName(); // Verify active DB
User::withoutTenantScoping()->get(); // Bypass tenant scoping
Custom Tenant Identifiers
Override Tenant::resolveByIdentifier() to support custom logic (e.g., UUIDs, emails).
Dynamic Database Switching
Extend the DatabaseResolver to support multi-tenant databases beyond the default schema:
// app/Providers/AppServiceProvider.php
public function boot() {
\Codeplace\MultitenancyBundle\Facades\Tenant::extend(function ($app) {
$app->bind(\Codeplace\MultitenancyBundle\Contracts\DatabaseResolver::class, function () {
return new CustomDatabaseResolver();
});
});
}
Tenant-Specific Config Load tenant-specific config files:
// config/multitenancy.php
'config_paths' => [
'tenants/{tenant_id}/config.php',
],
Soft Deletes
Ensure Tenant model uses SoftDeletes if tenants can be "disabled" without deletion:
use Illuminate\Database\Eloquent\SoftDeletes;
class Tenant extends Model {
use SoftDeletes;
}
How can I help you explore Laravel packages today?