Skip to main content
Tilbake til blogg
DevelopmentMarch 28, 202610 min

Multi-Tenant SaaS with Supabase: Architecture Patterns

Row-level security, tenant isolation, and real-time subscriptions — a practical guide to building multi-tenant apps on Supabase.

DB

David Bakke

Founder, Bakke & Co

PostShare
ForsidebildeDevelopment

Why Supabase for Multi-Tenant SaaS?

After building three SaaS products on Supabase (EventRipple, PortLink, and a client project), I've developed strong opinions about multi-tenant architecture on the platform.

Supabase gives you PostgreSQL with batteries included: auth, real-time subscriptions, storage, and edge functions. For multi-tenant SaaS, the killer feature is Row-Level Security (RLS) — database-level tenant isolation that's impossible to bypass from application code.

Pattern 1: Shared Database, RLS Isolation

This is our default pattern. All tenants share the same tables, with RLS policies enforcing isolation.

-- Every table has an org_id column
ALTER TABLE events ADD COLUMN org_id UUID REFERENCES organizations(id);

-- RLS policy: users can only see their org's data
CREATE POLICY "tenant_isolation" ON events
  FOR ALL
  USING (org_id = (SELECT org_id FROM user_profiles WHERE id = auth.uid()));

Pros

  • Simple schema, easy to maintain
  • Efficient queries (shared indexes)
  • Easy cross-tenant analytics (for you, not tenants)

Cons

  • Noisy neighbor risk (one tenant's heavy queries affect others)
  • Backup/restore is all-or-nothing

Pattern 2: Schema-Per-Tenant

For high-isolation requirements, create a PostgreSQL schema per tenant.

CREATE SCHEMA tenant_abc123;
CREATE TABLE tenant_abc123.events (LIKE public.events_template INCLUDING ALL);

We use this for PortLink where maritime companies require strict data isolation for compliance reasons.

Pattern 3: Real-Time Subscriptions

Supabase real-time is powerful but needs careful filtering in multi-tenant contexts.

const channel = supabase
  .channel('events')
  .on('postgres_changes', {
    event: '*',
    schema: 'public',
    table: 'events',
    filter: `org_id=eq.${orgId}`,
  }, handleChange)
  .subscribe();

Critical: Always filter by tenant ID in subscriptions. Without this, you'll receive changes from ALL tenants (RLS doesn't apply to real-time by default in older versions).

Lessons Learned

  1. Always enable RLS — even in development. Finding RLS bugs in production is painful.
  2. Test with multiple tenants from day one. Single-tenant testing hides isolation bugs.
  3. Use Supabase CLI for migrations. Never modify production schemas manually.
  4. Monitor query performance per tenant. One bad query pattern can affect everyone.

Based on production experience with Supabase across EventRipple, PortLink, and client projects.

SupabaseArchitectureSaaS