N
All writing
· 5 min read

Tenant isolation is a schema decision, not a middleware one

ArchitectureMulti-tenantLaravel

The failure mode everyone ships first

The intuitive way to build multi-tenancy is a middleware that reads the tenant from the request and stashes it in a context. Every controller then remembers to filter by that tenant. It works — until one query forgets.

That one forgotten where tenant_id = ? is not a bug you catch in code review. It's a data breach you find in a support ticket.

Make isolation structural

The fix is to stop relying on developers remembering. Scope isolation to the layer that touches every query:

  • A global query scope that injects the tenant predicate into every read.
  • A model event that stamps the tenant on every write.
  • Foreign keys that make a cross-tenant reference impossible to persist.

Now the safe path is the default path. A developer has to go out of their way to leak data, rather than out of their way to prevent it.

What it costs

You pay for this with a little ceremony: system jobs and admin tooling need an explicit, audited way to run without the scope. That escape hatch should be loud, centralized, and logged — not a boolean flag sprinkled across the codebase.

I'd rather have one well-guarded door than a hundred windows I have to remember to lock.