DEV Community

WordPress Multisite vs. Laravel Multi-Tenancy: Which Architecture Actually Scales?

You've got a client who wants to run multiple sites under one umbrella - think a franchise network, a SaaS product with per-customer subdomains, or a media group managing regional publications.

Two architectures immediately come to mind: WordPress Multisite and a custom Laravel multi-tenant application. Both can technically do the job. But "technically works" and "scales gracefully under production load" are very different claims.

This isn't a beginner's guide to either platform. If you've shipped production systems on both sides of this fence, you already know the tradeoffs exist. The goal here is to get specific - to look at where each architecture genuinely breaks down and what the code actually looks like when you're living with that decision six months later.

How WordPress Multisite Actually Works Under the Hood

WordPress Multisite shares a single database installation across sites, using table prefixes (wp_2_posts, wp_3_posts, etc.) to isolate content per site.

The wp_blogs table is your site registry, and WordPress switches context via switch_to_blog(int $blog_id) - a global state mutation that restores with restore_current_blog().

This model has a ceiling. Once you're running dozens of sites, queries against shared tables (wp_users, wp_usermeta) become contention points. Plugin authors rarely write Multisite-aware code, so you'll spend real engineering hours auditing third-party plugins for switch_to_blog compatibility - and find that most of them weren't designed with it in mind.

The horizontal scaling story is also awkward. Multisite assumes a shared filesystem for uploads (/wp-content/uploads/sites/{id}/). Moving to object storage helps, but you're bolting on what a modern architecture gives you by default.

Laravel Multi-Tenancy: Two Fundamental Approaches

In Laravel, multi-tenancy is an architectural choice, not a built-in feature. You're picking between single-database tenancy (shared schema, tenant column discrimination) and multi-database tenancy (one database or schema per tenant). The right call depends entirely on your isolation requirements.

Single-Database Tenancy with Global Scopes

For most SaaS applications, single-database tenancy is pragmatic. A tenant_id column on every relevant table, enforced via a global Eloquent scope:

// app/Models/Concerns/BelongsToTenant.php
trait BelongsToTenant {
  public static function bootBelongsToTenant(): void {
    static::addGlobalScope('tenant', function (Builder $query) {
      if ($tenantId = app('tenant.id')) {
        $query->where('tenant_id', $tenantId);
      }
    });
    static::creating(function (Model $model) {
      $model->tenant_id ??= app('tenant.id');
    });
  }
}

Your middleware resolves the tenant from the subdomain or domain and binds it to the container:

// app/Http/Middleware/ResolveTenant.php
public function handle(Request $request, Closure $next): Response {
  $host = $request->getHost();
  $tenant = Tenant::where('domain', $host)
    ->orWhere('subdomain', explode('.', $host)[0])
    ->firstOrFail();
  app()->instance('tenant.id', $tenant->id);
  app()->instance('tenant', $tenant);
  return $next($request);
}

This approach is easy to reason about, plays nicely with standard Eloquent tooling, and doesn't complicate your deployment. The tradeoff is that a misconfigured scope is a data leak - so your test suite needs to explicitly verify tenant isolation on every model that carries sensitive data.

Multi-Database Tenancy

When tenants need hard isolation - regulatory requirements, enterprise contracts, or just the peace of mind of separate databases - you switch database connections at runtime:

// app/Http/Middleware/ResolveTenant.php (multi-db variant)
public function handle(Request $request, Closure $next): Response {
  $tenant = Tenant::on('landlord')->where('domain', $request->getHost())->firstOrFail();
  config([
    'database.connections.tenant' => [
      'driver' => 'mysql',
      'host' => $tenant->db_host,
      'database' => $tenant->db_name,
      'username' => $tenant->db_user,
      'password' => $tenant->db_password,
    ]
  ]);
  DB::purge('tenant');
  DB::reconnect('tenant');
  DB::setDefaultConnection('tenant');
  return $next($request);
}

Migrations become interesting here. You'll want an artisan command that iterates every tenant database and runs pending migrations - and you need that to be atomic enough that a failed migration on tenant 47 doesn't silently skip tenants 48 through 200.

Tenant::all()->each(function (Tenant $tenant) {
  // Switch connection, run migrations, log outcome per tenant
  $this->switchToTenant($tenant);
  Artisan::call('migrate', ['--force' => true, '--path' => 'database/migrations/tenant']);
});

Packages like spatie/laravel-multitenancy and tenancy/tenancy handle much of this scaffolding and are worth evaluating before writing your own. The architectural concepts are the same; the question is whether the package's opinions align with your domain.

Where WordPress Multisite Still Wins

Content-heavy networks where non-technical editors need autonomy - this is where Multisite earns its keep. The editorial tooling, media management, and plugin ecosystem genuinely are unmatched for this use case. If your "tenants" are blog-style sites with minimal custom business logic, you're not fighting the platform.

The cost calculation also matters for smaller deployments. A developer working at an agency - the team at www.hanzweb.ae has navigated exactly this evaluation for clients across different sectors - will tell you that the break-even point between Multisite's low setup cost and Laravel's long-term flexibility usually lands somewhere around the complexity of your tenant-specific business logic, not the number of tenants.

The Real Performance Ceiling

WordPress Multisite under load degrades in predictable ways: wp_options autoloaded data grows per site, object cache pressure increases because you're effectively multiplying cache namespaces, and a single slow plugin can stall the entire request lifecycle.

Laravel's ceiling is higher, but it's not free to reach. Connection pooling via PgBouncer or ProxySQL becomes important at scale with multi-database tenancy. Queue workers need to be tenant-aware if you're dispatching jobs with tenant context. And your CI pipeline has to account for running migrations across a potentially large tenant set without downtime.

Making the Call

Ask these three questions before committing:

  1. Who controls the per-tenant customisation? If it's developers, Laravel. If it's editors using a GUI, WordPress.
  2. How different is the data model per tenant? Identical schema with different data = single-database tenancy. Meaningfully different schemas = multi-database or separate applications entirely.
  3. What's the compliance exposure? Any hard data isolation requirement (GDPR, HIPAA, enterprise contracts) pushes you toward multi-database or separate deployments - Multisite's shared tables are a liability.

Conclusion

Multisite and Laravel multi-tenancy aren't competing solutions to the same problem - they're solutions to different problems that superficially look the same. Multisite is a content management architecture that happens to support multiple sites. Laravel multi-tenancy is an application architecture that happens to serve multiple isolated users.

Get the domain model right first. Then pick the tool that makes that model easy to express and maintain. The architecture that feels like fighting the framework on day one will only get harder at day three hundred.

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.