Skip to main content
Softlimes Home
Softlimes
← Back to all articles•Development•8 min read

How We Build Multi-Tenant SaaS Applications and Client Portals in Laravel

Ivan

Ivan

Lead Engineer & Strategist at Softlimes

How We Build Multi-Tenant SaaS Applications and Client Portals in Laravel

Single Database vs Database-Per-Tenant Architecture

Every multi-tenant Laravel project starts with an architectural decision between shared database tables with a tenant_id column or isolated databases per customer. A single shared PostgreSQL database is simpler to migrate and cheaper to run when you have hundreds of small tenants with uniform data structures.

On the other hand, enterprise healthcare, finance, and legal clients often require physical data isolation, custom backup schedules, or region-specific residency. For those platforms, we use a control-plane database that stores tenant routing records and provisions a dedicated PostgreSQL database for each organization automatically.

Preventing Cross-Tenant Data Leaks With Global Eloquent Scopes

In a shared-database portal, the most dangerous bug is a developer forgetting a WHERE tenant_id clause on a report query or export job. Relying on engineers to remember manual query filters on every controller method guarantees a data leak eventually.

We enforce tenant scoping at the base Eloquent model level using a BelongsToTenant trait that applies a global query scope automatically on every read and write operation. When an authenticated request enters the application, middleware resolves the active tenant from the subdomain or session token and binds it to the service container.

  • Enforce NOT NULL foreign key constraints on tenant_id across every tenant-owned table in PostgreSQL.
  • Prefix all Redis cache keys and Horizon queued job payloads with the active tenant identifier.
  • Isolate uploaded customer files in S3 using tenant-specific bucket prefixes and signed temporary download URLs.
  • Write automated PHPUnit feature tests that assert User A from Tenant 1 receives a 404 when requesting any resource ID from Tenant 2.

Keeping Tenant Context Intact Inside Queues and Scheduled Commands

Web requests have an obvious logged-in user and subdomain, but background jobs and nightly cron tasks run outside the HTTP lifecycle. If a queued invoice generator runs without explicit tenant context, global model scopes may fail or query the wrong connection.

We configure our queue serialization middleware to attach the tenant ID to every dispatched job payload. When a Horizon worker picks up the job, it initializes the tenant database connection, sets the S3 storage root, and scopes the logger before executing a single line of business logic.

Role Permissions, Audit Logs, and Stripe Billing Hooks

B2B client portals also need granular internal permissions so a client manager can invite their own accounting clerk or field technician with restricted access. We pair tenant scoping with role-based permission tables and an immutable activity log that records who viewed, edited, or exported any record.

By combining automated Stripe subscription webhooks with per-tenant feature flags, your team can launch new pricing tiers, onboard enterprise organizations in seconds, and pass third-party security audits with confidence.

Before any new multi-tenant release ships to production, our continuous integration pipeline runs automated tenant-isolation assertions against every API route. That automated safety net guarantees that new features never introduce cross-account data exposure.

Related Softlimes Service

Laravel Development

See how our engineering team scopes, builds, and supports this workflow in production.

Explore Service →

Related articles by Ivan

Work With Us

Need engineering or marketing help on your next release

Talk directly with Ivan and the Softlimes engineering team to scope your web application, online store, AI workflow, or search campaign.