Build With Abdallah logo Build With Abdallah Software · AI · Automation
Tutorial 7 min read Jun 16, 2026

Building a Multi-Tenant SaaS Application with Laravel 13

What You'll Build

A
Abdallah Mohamed
Senior Full-Stack Engineer
Building a Multi-Tenant SaaS Application with Laravel 13

Building a Multi-Tenant SaaS Application with Laravel 13

What You'll Build

In this tutorial, you'll learn how to create a multi-tenant SaaS application using Laravel 13. The end product will be a web application that supports multiple tenants, each with their own isolated data. Users will be able to sign up, log in, and manage their own space independently. Here's a quick look at what you'll achieve:

  • Tenant Isolation: Each tenant will have its own database.
  • User Management: Users can register and log in under their respective tenants.
  • Admin Panel: A basic admin panel to manage tenants and users.

Why This Matters

Multi-tenancy is a crucial design pattern for SaaS applications. It allows you to serve multiple customers (tenants) with a single instance of your application, reducing operational costs and simplifying updates. This approach is particularly beneficial for:

  • Startups: Quickly scale your application to serve multiple clients without duplicating codebases.
  • Enterprises: Manage different departments or subsidiaries with isolated data but shared infrastructure.
  • Developers: Learn a valuable architectural pattern that enhances your ability to build scalable applications.

Architecture Overview

The architecture of a multi-tenant SaaS application involves components that ensure data isolation and efficient resource usage. Here’s a simple diagram to illustrate:

+-----------------+       +-----------------+
|   Tenant A DB   |       |   Tenant B DB   |
+-----------------+       +-----------------+
         |                        |
         +-----------+------------+
                     |
            +-----------------+
            |  Laravel App    |
            +-----------------+
                     |
              +---------------+
              |  User Access  |
              +---------------+
  • Database Per Tenant: Each tenant has its own database for data isolation.
  • Single Laravel Application: A single codebase handles requests for all tenants.
  • User Access Layer: Manages authentication and authorization across tenants.

Step-by-Step Implementation

Let's dive into building our multi-tenant SaaS application with Laravel 13.

Step 1: Set Up Laravel 13 Project

First, we need to set up a new Laravel project. Run the following command to create a new Laravel application:

composer create-project --prefer-dist laravel/laravel multi-tenant-saas

Navigate into your project directory:

cd multi-tenant-saas

Ensure everything is working by starting the local development server:

php artisan serve

Visit http://localhost:8000 in your browser to see the default Laravel welcome page.

Step 2: Configure Environment Variables

Next, we need to set up our environment variables for database connections. Open the .env file and configure the database settings. Since we are building a multi-tenant application, we'll use a default database for the initial setup:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=default_db
DB_USERNAME=root
DB_PASSWORD=your_password

This configuration connects Laravel to a MySQL database named default_db. Ensure that this database exists on your MySQL server.

Step 3: Create Authentication Scaffolding

Laravel provides a convenient way to generate authentication scaffolding. We'll use Laravel Breeze for a simple and clean setup.

First, install Laravel Breeze:

composer require laravel/breeze --dev

Then, run the Breeze installation command:

php artisan breeze:install

This command sets up authentication routes, controllers, and views. Next, run the migrations to create the necessary tables:

php artisan migrate

Finally, compile your assets:

npm install && npm run dev

At this point, you should be able to register and log in to the application. Visit http://localhost:8000/register to test the authentication functionality.

With these steps, we've laid the foundation for our multi-tenant application. In the next part of this tutorial, we'll focus on setting up tenant-specific databases and implementing tenant isolation.

Step 4: Implement Tenant Management

Now that we have the basic authentication setup, let's move on to tenant management. We'll create a simple system where each tenant has its own database. This involves setting up a model and migration for tenants.

First, create a Tenant model and migration:

php artisan make:model Tenant -m

Open the migration file created in database/migrations and define the schema for the tenants table:

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTenantsTable extends Migration
{
    public function up()
    {
        Schema::create('tenants', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('domain')->unique();
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('tenants');
    }
}

Run the migration to create the tenants table:

php artisan migrate

Step 5: Set Up Tenant Database Connections

For each tenant, we need to create a separate database. We will achieve this by dynamically switching the database connection based on the tenant's domain.

First, create a middleware to determine the current tenant:

php artisan make:middleware TenantMiddleware

In app/Http/Middleware/TenantMiddleware.php, implement the logic to resolve the tenant by its domain:

namespace App\Http\Middleware;

use Closure;
use App\Models\Tenant;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;

class TenantMiddleware
{
    public function handle($request, Closure $next)
    {
        $tenant = Tenant::where('domain', $request->getHost())->first();

        if (!$tenant) {
            abort(404, 'Tenant not found');
        }

        // Set the database connection for the tenant
        Config::set('database.connections.tenant.database', $tenant->database);
        DB::purge('tenant');

        return $next($request);
    }
}

In config/database.php, add a new connection for tenants:

'connections' => [
    // Other connections...

    'tenant' => [
        'driver' => 'mysql',
        'host' => env('DB_HOST', '127.0.0.1'),
        'port' => env('DB_PORT', '3306'),
        'database' => '',
        'username' => env('DB_USERNAME', 'root'),
        'password' => env('DB_PASSWORD', ''),
        'charset' => 'utf8mb4',
        'collation' => 'utf8mb4_unicode_ci',
        'prefix' => '',
        'strict' => true,
        'engine' => null,
    ],
],

Register the middleware in app/Http/Kernel.php:

protected $middlewareGroups = [
    'web' => [
        // Other middleware...
        \App\Http\Middleware\TenantMiddleware::class,
    ],
];

Step 6: Create Tenant-Specific Migrations

Finally, create tenant-specific migrations that will be run for each tenant database. You can use a command to automate this process. First, create a command:

php artisan make:command MigrateTenants

In app/Console/Commands/MigrateTenants.php, implement the command:

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\Tenant;
use Illuminate\Support\Facades\Artisan;

class MigrateTenants extends Command
{
    protected $signature = 'tenants:migrate';
    protected $description = 'Run migrations for all tenants';

    public function handle()
    {
        $tenants = Tenant::all();

        foreach ($tenants as $tenant) {
            $this->info("Migrating tenant: {$tenant->name}");

            config(['database.connections.tenant.database' => $tenant->database]);
            Artisan::call('migrate', [
                '--database' => 'tenant',
                '--path' => '/database/migrations/tenant',
                '--force' => true,
            ]);
        }

        return 0;
    }
}

Run this command whenever you add new migrations specific to tenant databases:

php artisan tenants:migrate

Common Mistakes

  1. Database Connection Issues: Forgetting to set the database name dynamically can lead to queries being run on the wrong database.
  2. Middleware Order: Ensure that the tenant middleware is correctly ordered in the middleware stack to avoid routing issues.
  3. Migration Path: Always specify the correct migration path for tenant databases to avoid running unwanted migrations.

How I Would Use This

This setup is ideal for SaaS products where data isolation is crucial. It's suitable for applications that need to scale across multiple clients with different data requirements. However, it can be overkill for small projects or those with minimal data isolation needs due to the complexity of managing multiple databases.

In production, consider the cost implications of managing multiple databases and ensure that your hosting solution supports this architecture.

Lessons Learned

  • Complexity: Multi-tenancy introduces complexity in database management and deployment processes.
  • Scalability: While this setup scales well for multiple tenants, it requires careful planning around database resources and query optimization.
  • Security: Ensuring data isolation is critical; improper configuration can lead to data leaks across tenants.

Next Steps

  • Implement advanced features like tenant-specific configurations or subdomains.
  • Explore Laravel packages like tenancy/tenancy for more robust multi-tenancy solutions.
  • Dive into performance optimization for multi-tenant applications.

Sources