Multi-Tenancy in Laravel: A Comprehensive Guide to the Database-Per-Tenant


Imagine building an application where each client feels like they have their own dedicated system, without the hassle of managing numerous deployments. That's the power of Multi-Tenancy, a software architecture that's changing how we deliver applications.
At its core, multi-tenancy means a single software application serves multiple customers, known as "tenants." It's like an apartment building: all residents (tenants) live in the same building (the software instance) and share the underlying infrastructure (servers, databases, etc.), but each has their own private apartment (their isolated data and configurations).
Why Multi-Tenancy?
Multi-tenancy offers significant advantages for SaaS providers and their customers:
Cost Efficiency: By sharing server, database, and application resources, providers can drastically reduce their infrastructure and operational costs. This often translates to more affordable services for tenants.
Scalability: With a single codebase and shared infrastructure, you can effortlessly onboard new tenants and scale your application horizontally, rather than spinning up entirely new environments for each customer. This means less operational overhead and more capacity for growth.
Simplified Maintenance and Updates: Updates, patches, and new features are applied to a single instance of the application. This means all tenants immediately benefit from improvements simultaneously, significantly reducing the maintenance burden for the provider.
Faster Onboarding: New tenants can be provisioned and ready to use the application quickly, often with just a few clicks, as no new software installation or complex infrastructure setup is required per customer.
Improved Efficiency: Shared resources are utilized more effectively across the tenant base. This leads to better overall performance and more optimized resource allocation, as peak loads from one tenant can be balanced with lighter loads from others.
For Laravel developers, the good news is that implementing multi-tenancy, especially the robust 'database per tenant' approach, is made significantly easier with powerful packages. The most prominent and highly recommended is stancl/tenancy
.
Multi-Tenancy Architecture: Database Per Organization
In this guide, we will focus on a highly isolated multi-tenancy strategy: a separate database for each organization (tenant). This approach offers the highest level of data isolation and security. It's like giving each tenant their own locked vault โ their data is completely separate, which is crucial for compliance and peace of mind. It also provides better performance guarantees for large tenants and simplifies individual tenant backups and restores.
How Tenant Identification Works (Subdomains)
A common and highly effective strategy for tenant identification is using subdomains. Imagine your main application residing at yourdomain.com
. For each organization, you'll provision a unique subdomain, like https://organizationA.yourdomain.com
or https://clientB.yourdomain.com
. This instantly routes requests to the correct tenant context, ensuring their data remains isolated and secure. When a user logs in via https://organizationA.yourdomain.com
, the application automatically connects them to "Organization A's" dedicated database.
Example URL Structure:
https://acme.yourdomain.com
(for the Acme organization)https://globex.yourdomain.com
(for the Globex organization)https://central.yourdomain.com
(for your main marketing site or administration panel)
Let's Dive Into the Implementation with Laravel!
Now that we understand the 'what' and 'why' of multi-tenancy with a separate database per organization, let's roll up our sleeves and dive into the implementation using Laravel and stancl/tenancy
.
Prerequisites:
Before we begin, ensure you have:
A fresh Laravel project set up on your machine. You can use the official Laravel documentation for a standard setup or leverage a starter kit.
Basic understanding of Laravel routing, Eloquent ORM, and database migrations.
Composer installed globally.
(Optional but recommended for local subdomain testing) A local development environment like Laravel Valet or Homestead configured to handle wildcards or subdomains.
Step 1: Install stancl/tenancy
The first step is to pull in the stancl/tenancy
package via Composer. This package will handle the heavy lifting of tenant identification, database switching, and more.
Open your terminal in your Laravel project's root directory and run:
composer require stancl/tenancy
Then, run the tenancy:install
command:
php artisan tenancy:install
This will create a few files: migrations, config file, route file and a service provider.
Let's run the migrations:
php artisan migrate
Register the service provider in bootstrap/providers.php
:
return [
App\Providers\AppServiceProvider::class,
App\Providers\TenancyServiceProvider::class, // <-- here
];
Creating a tenant model
Now you need to create a Tenant model with this command or manually php artisan make:mode Tenant
. The package comes with a default Tenant model that has many features, but it attempts to be mostly unopinionated and as such, we need to create a custom model to use domains & databases. Create the file app/Models/Tenant.php
like this:
<?php
namespace App\Models;
use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\Concerns\HasDatabase;
use Stancl\Tenancy\Database\Concerns\HasDomains;
class Tenant extends BaseTenant implements TenantWithDatabase
{
use HasDatabase, HasDomains;
}
Please note: if you have the models anywhere else with same name, you should adjust the code and commands of this tutorial accordingly.
Now we need to tell the package to use this custom model. Open the config/tenancy.php
file and modify the line below:
'tenant_model' => \App\Models\Tenant::class,
Central domains
Now we need to actually specify the central domains. A central domain is a domain that serves your "central app" content, e.g. the landing page where tenants sign up. Open the config/tenancy.php
file and add them in:
'central_domains' => [
'saas.test', // Add the ones that you use. I use this one with Laravel Valet.
],
If you're using Laravel Sail, no changes are needed, default values are good to go:
'central_domains' => [
'127.0.0.1',
'localhost',
],
Once all these configurations are done, you can find the tenant.php
file in your routes
directory. This file is dedicated to handling all routes that belong to your individual tenants.
// routes/tenant.php
<?php
declare(strict_types=1);
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Middleware\PreventAccessFromCentralDomains;
use Inertia\Inertia;
/*
|--------------------------------------------------------------------------
| Tenant Routes
|--------------------------------------------------------------------------
|
| Here you can register the tenant routes for your application.
| These routes are loaded by the TenantRouteServiceProvider.
|
| Feel free to customize them however you want. Good luck!
|
*/
Route::middleware([
'web',
InitializeTenancyByDomain::class, // This middleware identifies the tenant based on the domain.
PreventAccessFromCentralDomains::class, // This middleware ensures these routes are not accessible from central domains.
])->group(function () {
Route::get('/', function () {
// After a user logs into their specific tenant subdomain (e.g., acme.yourdomain.com),
// all requests handled by routes in this file will automatically operate within that tenant's context.
// `tenant('id')` is a helper provided by stancl/tenancy to get the ID of the currently resolved tenant.
return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id');
});
// You can define all your tenant-specific application routes here.
// For example, dashboard, user management, product listings, etc., for a specific organization.
// Route::get('/dashboard', [TenantDashboardController::class, 'index']);
// Route::resource('products', TenantProductController::class);
});
You can handle all tenant-wise routes inside the routes/tenant.php
file. For instance, after a user has logged into their tenant-specific subdomain (e.g., acme.yourdomain.com
), all subsequent requests for the tenant's application (like /dashboard
, /users
, /products
) will be routed through this file and will automatically belong to that specific tenant's context.
And as you're already aware, Laravel also has the web.php
file in the routes
directory to handle central web-level requests. This is where you'll define routes that are common to all users or handle interactions before a tenant is identified (like your main landing page, registration, and central login).
// routes/web.php
<?php
use Illuminate\Support\Facades\Route;
// This loop ensures that the following routes only respond on your defined central domains.
foreach (config('tenancy.central_domains') as $domain) {
Route::domain($domain)->group(function () {
Route::get('/', function () {
return "Central Login or Marketing Page";
});
// Add your central routes here, e.g., registration, password reset, public pages
// Route::get('/register-tenant', [TenantRegistrationController::class, 'showRegistrationForm'])->name('tenant.register.form');
// Route::post('/register-tenant', [TenantRegistrationController::class, 'register'])->name('tenant.register');
});
}
Here, you can manage all the central routes for your application. These routes are accessible from your primary domain (e.g., yourdomain.com
) and are not tied to any specific tenant's database or context.
Finally, a quick check to your database/migrations
path. You'll notice a tenant
subdirectory has been created (or you created it manually). This is crucial! Any database tables that should exist within each individual tenant's database (e.g., users
, products
, orders
specific to that organization) need their migration files to be placed inside this database/migrations/tenant
directory. When a new tenant is created via stancl/tenancy
, the migrations in this dedicated folder will be automatically run on their newly provisioned database. This ensures complete isolation of tenant data.
Database Migrations: Managing Central and Tenant Schemas
As mentioned, a key aspect of the "database per tenant" strategy is the separation of your database schemas.
First, ensure that all migration files for tables that should exist within each individual tenant's database are located inside the database/migrations/tenant
directory. For example, if each organization (tenant) has its own set of users, products, or orders, the migrations for these tables belong here. In this demonstration, we'll focus on having a users
table specific to each tenant. So created the user table inside the tenant folder.
Any migrations that define tables for your central application (like the tenants
table itself, subscription plans, or central admin users) will remain in the main database/migrations
directory.
To run the migrations for your central database (where the tenants
table resides), you'll use the standard Laravel command:
Bash
php artisan migrate
To run the migrations for your tenants' databases (e.g., to create the users
table in each tenant's database), stancl/tenancy
provides a dedicated command:
Bash
php artisan tenants:migrate --tenants=<your-tenant-name>
This command will iterate through all your registered tenants and apply the migrations found in database/migrations/tenant
to each of their respective databases.
Authentication with Laravel Fortify
For handling authentication, this demonstration utilizes Laravel Fortify. Fortify provides the backend logic for common authentication tasks like login, registration, password reset, and email verification, allowing you to build your own frontend UI on top of it. You can find detailed setup instructions for Laravel Fortify in the Official Laravel Documentation.
Since we're using Inertia.js with React for the frontend, our existing starter kit already provides the basic login, registration, and dashboard UI components. You can locate these React components within your Laravel project under the resources/js/Pages
directory.
Fortify offers default authentication routes (e.g., /login
, /logout
, /register
, /reset-password
). If you don't explicitly define these routes in your routes
files, Fortify will handle them automatically. Our primary task is to inform Fortify which UI component to render for these authentication flows.
You'll typically do this by configuring the and add the necessary Inertia component paths within the boot
method:
here below give the authentication and login configuration in the fortifyServiceProvider file.
By default, after successful authentication, Fortify redirects the user to the /home
route. If you wish to change this redirect destination (for example, to /dashboard
or a tenant-specific route), you can modify the home
option in your config/fortify.php
configuration file.
Tenant Redirection and Authentication
With the basic setup and authentication complete, the next crucial step is to handle user redirection from the central login to the correct tenant's environment. This process ensures a seamless and secure multi-tenant experience.
Our strategy for managing this involves a key concept: synchronizing user data between the central database and each tenant's database.
Centralized User Management: We will maintain a central
users
table for your main application, which will include atenant_id
to link each user to their specific tenant. This central user record is essential for the initial login and tenant identification.Tenant-Specific User Records: Concurrently, each tenant's database will also have its own
users
table. This table will contain user information specific to that tenant, such as roles, permissions, and settings within their isolated environment.
This dual-database approach ensures that while user credentials can be validated centrally, all a user's operational data remains securely within their dedicated tenant database.
Implementing the Login and Redirection Logic
To achieve a truly seamless multi-tenant experience, we need to carefully manage the login and redirection flow. This involves a custom approach that works in conjunction with Laravel Fortify's default authentication mechanisms.
First, it's crucial that your frontend login form submits to Fortify's default login route. This allows Fortify to handle the initial authentication process against your central database.
Custom Login Response for Tenant Redirection
Upon successful authentication with Fortify, instead of a standard redirect, we'll implement a custom login response. This custom response will be responsible for orchestrating the redirection to the appropriate tenant URL
You will typically define this custom logic within a custom action or response class, which you can then register in your AppServiceProvider
(or a dedicated FortifyServiceProvider
).
Within this custom logic, the following steps will occur:
Verify Central Authentication: Confirm the user has successfully authenticated against the central
users
table.Retrieve Tenant Information: Fetch the
tenant_id
associated with the authenticated user from the central database. This ID is crucial for constructing the correct tenant URL.Generate Authentication Token: For secure redirection and immediate authentication on the tenant's domain, generate a temporary, signed authentication token (e.g., a JWT or a simple signed URL parameter). This token will be embedded in the redirection URL.
Construct Tenant URL: Dynamically build the target tenant URL, such as
https://{tenant-slug}.
yourdomain.com/auth-token?token={your_generated_token}
.Frontend Redirection: The custom login response will then instruct the frontend to perform a client-side redirect to this constructed tenant URL.
Tenant-Specific Authentication Handler
Once the user is redirected to their tenant's subdomain (e.g., https://acme.yourdomain.com/auth-token?token=
...
), stancl/tenancy
will automatically identify the tenant and switch to their dedicated database context.
At this point, a tenant-specific route (e.g., /auth-token
as defined in routes/tenant.php
) will handle the incoming request. This route will be secured by middleware that initializes the tenant based on the domain.
Within the controller for this route (e.g., TenantAuthController
):
Validate Token: The controller will retrieve and validate the authentication token passed in the URL.
Authenticate Tenant User: Based on the token, the controller will then authenticate the user within the tenant's specific
users
table. This might involve finding the user record by email or a unique identifier and logging them in via Laravel'sAuth
facade.Final Redirect: After successful tenant-level authentication, the user can be redirected to their tenant dashboard (e.g.,
/dashboard
or/home
), completing the seamless login experience.
By following this approach, we ensure a authentication flow that correctly routes users to their dedicated tenant space.
What's Next? ๐
You've successfully set up a multi-tenant application using Laravel and stancl/tenancy
!
This architecture provides the foundation for building powerful SaaS products where each client feels like they have their own dedicated system.
From here, you can continue to build out your application by:
Implementing a tenant registration flow on your central domain that automatically provisions a new tenant and its database.
Integrating a billing and subscription service (like Stripe or Laravel Cashier) to manage different plans for your tenants.
Building out the tenant-specific application logic in your
routes/tenant.php
file, including dashboards, reporting, and custom features.
The power of multi-tenancy is in its scalability and efficiency. With this foundation, you're well-equipped to grow your application from a single client to thousands, all managed from a single, streamlined codebase.
Happy coding!
Subscribe to my newsletter
Read articles from Devapraveen directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Devapraveen
Devapraveen
Passionate full-stack developer from india. Sharing knowledge and experiences through my blog to help fellow developers tackle challenges and navigate the evolving world of software development.