DEV Community

Cover image for Build a Multi-Tenant SaaS in Laravel 🏢
Prajapati Paresh
Prajapati Paresh

Posted on • Originally published at smarttechdevs.in

Build a Multi-Tenant SaaS in Laravel 🏢

The SaaS Scaling Challenge

When building B2B SaaS applications, you inevitably hit the "Tenancy" crossroad. You have hundreds of companies using your platform, and you must ensure that Company A never accidentally sees Company B's data. You could deploy a separate database for every client, but managing 500 databases quickly becomes an infrastructure nightmare.

At Smart Tech Devs, we prefer the Single-Database Multi-Tenancy approach. It allows us to keep infrastructure costs low while ensuring absolute data isolation at the application level.

Data Isolation via Global Scopes

The biggest risk in a single-database architecture is a developer forgetting to add a where('tenant_id', $id) clause to an Eloquent query. To solve this, we remove the human element entirely using Laravel's Global Scopes.

Step 1: The Tenant Scope

We define a Global Scope that automatically appends a tenant filter to every query executed on a model.


namespace App\Models\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Support\Facades\Auth;

class TenantScope implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        // Automatically scope queries to the currently authenticated user's tenant
        if (Auth::hasUser() && Auth::user()->tenant_id) {
            $builder->where('tenant_id', Auth::user()->tenant_id);
        }
    }
}

Step 2: Applying the Trait

Instead of manually adding this scope to every model, we create a reusable trait. Any model (e.g., Invoices, Employees, Projects) that belongs to a tenant simply uses this trait.


namespace App\Traits;

use App\Models\Scopes\TenantScope;

trait BelongsToTenant
{
    protected static function bootBelongsToTenant()
    {
        static::addGlobalScope(new TenantScope);

        // Automatically assign the tenant_id when creating a new record
        static::creating(function ($model) {
            if (session()->has('tenant_id')) {
                $model->tenant_id = session()->get('tenant_id');
            }
        });
    }
}

The Engineering ROI

By enforcing tenancy at the ORM level, developers can write simple queries like Invoice::all() without worrying about cross-client data leaks. Laravel automatically handles the filtering behind the scenes. This architecture allows you to scale a SaaS to thousands of tenants on a single, easily maintainable database while maintaining enterprise-grade data security.

Top comments (0)