Skip to content

Multi-tenancy

First-class Filament tenant scoping is on the roadmap. This page describes what works today and how to scope approvals to a tenant in the meantime.

How flows are matched today

submitForApproval() resolves a flow by model type only:

ApprovalFlow::activeFor($record::class);

There is no built-in tenant column on flows yet, so in a multi-tenant app you have two solid options.

Skip auto-resolution and hand the run the tenant's flow. This keeps you in full control and needs no plugin changes:

$flow = $tenant->approvalFlows()   // your own relation / lookup
    ->where('model_type', PurchaseOrder::class)
    ->where('is_active', true)
    ->firstOrFail();

$record->submitForApproval(flow: $flow);

Store the tenant key on the flow yourself (e.g. a team_id column added in your own migration) and query it however your app models tenancy.

Option B — tenant-aware approver resolution

Even with a shared flow, keep approvers tenant-correct by writing a custom resolver that filters users to the current tenant:

public function resolve(ApprovalStep $step): Collection
{
    return User::role($step->approver_role)
        ->where('team_id', Filament::getTenant()?->getKey())
        ->get();
}

Because the Inbox uses its own query, override it too so users only see approvals for their tenant — filter on the approvable's tenant relation.

Scoping what users see

The Approval Inbox lists everything the user can act on across the app. In a tenant panel, constrain it by extending the inbox page and scoping the base query to the active tenant (e.g. whereHasMorph('approvable', ..., fn ($q) => $q->where('team_id', $tenant->id))).

What's coming

Planned first-class support will add an optional tenant column to flows, tenant-scoped activeFor(), and a tenant-aware Inbox out of the box, matching Filament's native tenancy. Track progress on the roadmap. Until then, Options A and B cover production multi-tenant use.

Next: Upgrade (v4 ↔ v5).