I built a multi-branch business system in Laravel. Each branch must only see its own invoices, clients, and payments. A global scope solved that in a few lines. Then the bugs started.

Here is the setup, and the four problems I had to fix.

The trait

trait BelongsToBranch { protected static function bootBelongsToBranch(): void { static::addGlobalScope('branch', function (Builder $query) { $user = auth()->user(); if (! $user || $user->isAdmin()) { return; } $query->where( $query->getModel()->getTable() . '.branch_id', $user->branch_id ); }); static::creating(function ($model) { if (auth()->check() && empty($model->branch_id)) { $model->branch_id = auth()->user()->branch_id; } }); } } 

Add use BelongsToBranch; to any model, and every query is filtered automatically.

Bug 1: "Column 'branch_id' is ambiguous"

My first version used where('branch_id', ...). It broke as soon as a query joined two tables that both had branch_id.

Fix: always qualify the column with the table name, as in the trait above.

Bug 2: Shared records disappear inside relations

Some clients are shared across branches. When a branch user opened an invoice, $invoice->client returned null, because the scope also ran on the related query.

Fix: remove the scope on that specific relation:

public function client() { return $this->belongsTo(Client::class) ->withoutGlobalScope('branch'); } 

The hard part is consistency. If you remove it in one place but not in an eager load or a whereHas, the same record shows on one page and vanishes on another.

Bug 3: Queued jobs see everything

Jobs and commands run without a logged-in user. My scope returned early when there was no user, so a queued report job read data from all branches.

Fix: never rely on auth() inside jobs. Pass the branch explicitly:

Invoice::withoutGlobalScope('branch') ->where('branch_id', $this->branchId) ->get(); 

Bug 4: Unique validation ignores the scope

Rule::unique('invoices', 'number') queries the table directly. Global scopes don't apply, so invoice number INV-001 in branch A blocked INV-001 in branch B.

Fix: add the branch condition yourself:

Rule::unique('invoices', 'number') ->where('branch_id', auth()->user()->branch_id) 

The same applies to raw DB::table() queries. Global scopes only work through Eloquent.

Takeaway

Global scopes are a clean way to isolate data, but they work silently. Test three cases every time: a branch user, an admin, and a queued job.

I build multi-branch and SaaS systems in Laravel. If you need one, you can find me here: [YOUR ADMIN PANEL / SAAS GIG LINK]