Multi-Tenant Laravel SaaS: Query Scoping &amp; Data Leaks | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com) [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles  ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

  [ home ](https://www.msaied.com)    [ articles ](https://www.msaied.com/articles)    Multi-Tenant SaaS with Laravel: Scoping Queries, Resolving Tenants, and Avoiding Data Leaks        On this page       1. [  The Core Problem: Implicit vs. Explicit Tenancy ](#the-core-problem-implicit-vs-explicit-tenancy)
2. [  Step 1: Resolve the Tenant Early ](#step-1-resolve-the-tenant-early)
3. [  Step 2: Enforce Isolation with a Global Scope ](#step-2-enforce-isolation-with-a-global-scope)
4. [  Step 3: The Pitfalls That Leak Data ](#step-3-the-pitfalls-that-leak-data)
5. [  Queued Jobs ](#queued-jobs)
6. [  withoutGlobalScope in Tests ](#codewithoutglobalscopecode-in-tests)
7. [  Raw Queries and DB Facade ](#raw-queries-and-db-facade)
8. [  Step 4: Verify Isolation with an Architecture Test ](#step-4-verify-isolation-with-an-architecture-test)
9. [  Takeaways ](#takeaways)

  ![Multi-Tenant SaaS with Laravel: Scoping Queries, Resolving Tenants, and Avoiding Data Leaks](https://cdn.msaied.com/200/2c359f1a5609a6c7ced2a5d48b93249c.png)

  #laravel   #multi-tenancy   #saas   #eloquent   #architecture  

 Multi-Tenant SaaS with Laravel: Scoping Queries, Resolving Tenants, and Avoiding Data Leaks 
=============================================================================================

     15 Jun 2026      4 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

  9 sections  

1. [  01   The Core Problem: Implicit vs. Explicit Tenancy  ](#the-core-problem-implicit-vs-explicit-tenancy)
2. [  02   Step 1: Resolve the Tenant Early  ](#step-1-resolve-the-tenant-early)
3. [  03   Step 2: Enforce Isolation with a Global Scope  ](#step-2-enforce-isolation-with-a-global-scope)
4. [  04   Step 3: The Pitfalls That Leak Data  ](#step-3-the-pitfalls-that-leak-data)
5. [  05   Queued Jobs  ](#queued-jobs)
6. [  06   withoutGlobalScope in Tests  ](#codewithoutglobalscopecode-in-tests)
7. [  07   Raw Queries and DB Facade  ](#raw-queries-and-db-facade)
8. [  08   Step 4: Verify Isolation with an Architecture Test  ](#step-4-verify-isolation-with-an-architecture-test)
9. [  09   Takeaways  ](#takeaways)

       The Core Problem: Implicit vs. Explicit Tenancy
-----------------------------------------------

Most multi-tenant Laravel apps start with a `tenant_id` column on every table and a `where('tenant_id', $current)` sprinkled throughout controllers. That works until a developer forgets one clause and a customer sees another customer's data. The fix is to make tenancy *structural*, not optional.

This article focuses on the **single-database, shared-schema** model — the most common starting point for SaaS — and shows how to make tenant isolation automatic and testable.

---

Step 1: Resolve the Tenant Early
--------------------------------

Create a `TenantResolver` service that extracts the tenant from the request (subdomain, custom header, or JWT claim) and binds it into the container as a singleton for the request lifecycle.

```php
// app/Tenancy/TenantResolver.php
class TenantResolver
{
    public function fromRequest(Request $request): Tenant
    {
        $host = $request->getHost(); // e.g. acme.app.test
        $subdomain = explode('.', $host)[0];

        return Tenant::where('slug', $subdomain)
            ->firstOrFail();
    }
}

```

Bind it in a middleware that runs before any route logic:

```php
// app/Http/Middleware/IdentifyTenant.php
public function handle(Request $request, Closure $next): Response
{
    $tenant = app(TenantResolver::class)->fromRequest($request);

    app()->instance(Tenant::class, $tenant);
    app()->instance('current.tenant', $tenant);

    return $next($request);
}

```

Register it in `bootstrap/app.php` (Laravel 11+) or `Kernel.php` before your route middleware group.

---

Step 2: Enforce Isolation with a Global Scope
---------------------------------------------

A `GlobalScope` applied to every tenant-owned model is the safest mechanism. It runs on every `SELECT`, `UPDATE`, and `DELETE` automatically.

```php
// app/Tenancy/TenantScope.php
use Illuminate\Database\Eloquent\{Builder, Model, Scope};

class TenantScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        $tenant = app('current.tenant');

        $builder->where(
            $model->getTable() . '.tenant_id',
            $tenant->id
        );
    }
}

```

Create a `BelongsToTenant` trait to keep models clean:

```php
trait BelongsToTenant
{
    public static function bootBelongsToTenant(): void
    {
        static::addGlobalScope(new TenantScope());

        static::creating(function (Model $model) {
            if (empty($model->tenant_id)) {
                $model->tenant_id = app('current.tenant')->id;
            }
        });
    }
}

```

Now any model using this trait is automatically scoped:

```php
class Project extends Model
{
    use BelongsToTenant;
}

// This query is automatically WHERE tenant_id = ? under the hood
$projects = Project::where('status', 'active')->get();

```

---

Step 3: The Pitfalls That Leak Data
-----------------------------------

### Queued Jobs

The container binding is request-scoped. A queued job runs in a fresh process with no HTTP request. Always serialize the tenant ID on the job and re-bind it in `handle()`:

```php
class ProcessInvoice implements ShouldQueue
{
    public function __construct(
        public readonly int $tenantId,
        public readonly int $invoiceId,
    ) {}

    public function handle(): void
    {
        $tenant = Tenant::findOrFail($this->tenantId);
        app()->instance('current.tenant', $tenant);

        $invoice = Invoice::findOrFail($this->invoiceId); // scoped
    }
}

```

### `withoutGlobalScope` in Tests

Test helpers that call `withoutGlobalScopes()` to "simplify" setup silently disable your entire isolation layer. Instead, bind a test tenant in `setUp()`:

```php
beforeEach(function () {
    $this->tenant = Tenant::factory()->create();
    app()->instance('current.tenant', $this->tenant);
});

```

### Raw Queries and DB Facade

Global scopes do not apply to `DB::select()` or `DB::statement()`. Audit every raw query and pass `tenant_id` explicitly, or wrap them in a `TenantAwareQuery` helper that injects the clause.

---

Step 4: Verify Isolation with an Architecture Test
--------------------------------------------------

```php
// tests/Architecture/TenancyTest.php
arch('tenant-owned models use BelongsToTenant')
    ->expect('App\\Models')
    ->toUseTrait('App\\Tenancy\\BelongsToTenant')
    ->ignoring(['App\\Models\\Tenant', 'App\\Models\\User']);

```

This Pest architecture test fails CI the moment a developer adds a new model without the trait.

---

Takeaways
---------

- Resolve the tenant once in middleware and bind it as a container singleton — never pass it through method arguments.
- Use a `GlobalScope` + trait combo so isolation is opt-out, not opt-in.
- Queued jobs must re-bind the tenant; the HTTP request context does not carry over.
- Raw `DB::` calls bypass global scopes entirely — treat them as a security boundary.
- An architecture test that enforces trait usage catches omissions before they reach production.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Fmulti-tenant-saas-with-laravel-scoping-queries-resolving-tenants-and-avoiding-data-leaks&text=Multi-Tenant+SaaS+with+Laravel%3A+Scoping+Queries%2C+Resolving+Tenants%2C+and+Avoiding+Data+Leaks) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Fmulti-tenant-saas-with-laravel-scoping-queries-resolving-tenants-and-avoiding-data-leaks) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Does a GlobalScope affect UPDATE and DELETE statements in Eloquent?        Yes. Eloquent's `update()` and `delete()` methods go through the query builder which applies all registered global scopes, so your tenant_id constraint is included automatically. Direct `DB::update()` calls are not affected. 

      Q02  How do I temporarily bypass the tenant scope for super-admin operations?        Use `Model::withoutGlobalScope(TenantScope::class)` for a specific query, or `Model::withoutGlobalScopes()` to remove all scopes. Wrap these calls in a dedicated admin service class so they are easy to audit and never leak into normal request paths. 

      Q03  Should I use separate databases per tenant instead of a shared schema?        Separate databases offer stronger isolation and simpler backups per tenant, but they increase operational complexity significantly (migrations across hundreds of databases, connection pool exhaustion). Shared-schema with global scopes is the right default until you have a compliance or performance reason to switch. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://www.msaied.com/articles) 

 [ ![Laravel Telescope Alternatives: Building a Lightweight Request Inspector with Middleware](https://cdn.msaied.com/216/9b6d240010b80483f072902dafcd216c.png) laravel middleware debugging 

### Laravel Telescope Alternatives: Building a Lightweight Request Inspector with Middleware

Telescope is powerful but heavy for production. Learn how to build a focused, low-overhead request inspector u...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 16 Jun 2026     1 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-telescope-alternatives-building-a-lightweight-request-inspector-with-middleware) [ ![RAG Pipelines in Laravel: Chunking, Embedding, and Retrieval with pgvector](https://cdn.msaied.com/215/e037e13535aa77822f879ee829ec3f68.png) laravel ai pgvector 

### RAG Pipelines in Laravel: Chunking, Embedding, and Retrieval with pgvector

Build a production-ready Retrieval-Augmented Generation pipeline in Laravel using pgvector, OpenAI embeddings,...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 16 Jun 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/rag-pipelines-in-laravel-chunking-embedding-and-retrieval-with-pgvector) [ ![Laravel Pest: Architecture Tests, Mutation Testing, and Type Coverage in CI](https://cdn.msaied.com/214/0d4822fa8ee1765d0689e387dd849d92.png) laravel pest testing 

### Laravel Pest: Architecture Tests, Mutation Testing, and Type Coverage in CI

Go beyond feature tests. Learn how to enforce architectural rules, catch logic gaps with mutation testing, and...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 16 Jun 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-pest-architecture-tests-mutation-testing-and-type-coverage-in-ci) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
