Fewer vendors → less integration work → faster to ship
Three Differentiators
Open Source
97K+ GitHub stars
Self-hostable on your infra
Building in public
Postgres-Native
Standard SQL, not a proprietary query language
Full extension ecosystem
pgvector, pg_cron, PostGIS
No Lock-in
pg_dump anytime
Standard connection strings
Your data, your keys
Self-host or migrate whenever
Security at the Database Layer
Row Level Security — authorization enforced by Postgres, not your app code
CREATE POLICY "own posts"ON posts FORALLTOauthenticatedUSING (user_id = auth.uid()) -- who can SEE / touch this rowWITHCHECK(user_id = auth.uid()); -- what can be WRITTEN into this row
Clause
Applies to
Guards against
USING
SELECT, UPDATE, DELETE
Reading or deleting someone else’s rows
WITH CHECK
INSERT, UPDATE
Writing a row with the wrong owner — or reassigning ownership to another user
Without USING: you can update rows you don’t own. Without WITH CHECK: you can update your own row and change user_id to someone else’s.
Both together = you can only touch rows you own, and you can’t transfer ownership.
What Customers Are Building
Pattern
Example
Why Supabase
Startup greenfield
Chatbase ($1M rev in 5 months)
Ship in days, not weeks
Firebase migration
Good Tape (60% cost cut)
Standard SQL + no per-read billing
Multi-tenant SaaS
RLS per-tenant policies
Authorization at DB layer, not app layer
AI / vector
pgvector for embeddings
One less vendor — same DB
EU compliance
GDPR + data residency
6 EU regions, SOC 2, custom DPA
Pasteriser — paste.erfi.io
A production pastebin — every Supabase surface used deliberately.
Feature
What I used
Data + search
Postgres, 14 migrations, FTS via tsvector + GIN index
Access control
7 RLS policies — public read, owner write, burn-after-read
Auth
Email, magic link, GitHub OAuth with PKCE in a CF Worker
Live feed
Realtime broadcast from a Postgres trigger
Scheduled cleanup
pg_cron — replaces Lambda + EventBridge entirely
Config as code
supabase/config.toml + config push
282 tests — including race-condition tests under 100 concurrent hits.
Summary
What
Postgres + Auth + Storage
Realtime + Edge Functions
One platform, one bill
Why
Open source, self-hostable
Postgres-native, no lock-in
Security at the DB layer
Who
Startups shipping fast
Firebase/Auth0 migrations
AI teams adding vector search
Questions?
Reference
How Full-Text Search Works
Search that understands language — not just exact word matching.
Step
What happens
1. Write a paste
Postgres converts title + language to root words: "running tests" → run test
2. Store the index
The root words are stored in a tsvector column — automatically kept in sync
3. Build a GIN index
A lookup table maps every root word to which rows contain it
4. Search
User types "run" — Postgres checks the index, returns matching rows in milliseconds
No Elasticsearch. No separate search service. Built into Postgres.
Realtime: Database → Browser
A browser receives an update the moment a row is written — no polling.
User saves a paste
↓
Postgres commits the row
↓
AFTER INSERT trigger fires
↓
realtime.send() → Realtime server → all subscribed browsers
Two functions:
realtime.send() — you control the payload (Pasteriser: strips private columns)
realtime.broadcast_changes() — sends the full row change (acme-corp notes feed)
pg_cron: Scheduled Tasks in Postgres
Run SQL on a schedule, from inside the database. No extra services.
-- Delete expired pastes every 5 minutesSELECT cron.schedule('cleanup-expired','*/5 * * * *', $$ DELETEFROM pastes WHERE expires_at < now() $$);
AWS Lambda + EventBridge
pg_cron
Where it runs
Separate serverless service
Inside Postgres
Deployment
Deploy function + create rule
One SQL statement
Logs
CloudWatch
cron.job_run_details table
Cost
Per invocation + rule
Included with Postgres
Firebase → Supabase: Product Map
Firebase product
What it does
Supabase equivalent
Firestore
NoSQL document database — nested collections/documents, no SQL
Postgres — relational tables, standard SQL. Biggest conceptual shift.
Firebase Auth
Email, Google/Apple login, phone OTP
Supabase Auth — near 1:1 feature parity
Firebase Storage
File uploads (photos, videos, docs)
Supabase Storage — S3-compatible, RLS policies on files
Cloud Functions (HTTP)
Serverless code triggered by HTTP calls
Edge Functions — Deno runtime, same idea
Cloud Functions (events)
Code triggered by DB changes, new users, etc.
Database triggers — SQL functions that fire on row changes
Cloud Functions (scheduled)
Code on a cron schedule
pg_cron — SQL on a schedule, inside Postgres
FCM
Push notifications to phones/browsers
Edge Function + DB webhook → calls FCM or Expo — FCM still delivers, Supabase is the trigger
Multi-Tenant: The Schema
One database, many companies — each seeing only their own data.
-- Every company is a tenantCREATETABLE tenants (id uuid PRIMARYKEYDEFAULT gen_random_uuid(), name text NOTNULL);-- Every user belongs to one tenantCREATETABLE profiles (id uuid PRIMARYKEYREFERENCES auth.users(id) ONDELETECASCADE, tenant_id uuid REFERENCES tenants(id));-- Business data carries tenant_id on every rowALTERTABLE notes ADDCOLUMN tenant_id uuid REFERENCES tenants(id);
When a new user signs up, a database trigger automatically creates their tenant and profile. No application code needed.
Multi-Tenant: Pattern 1
Membership table lookup — look up the user’s tenant at query time
CREATE POLICY "view own tenant's notes"ON notesFORSELECTUSING ( tenant_id IN (SELECT tenant_id FROM profiles WHEREid= (SELECT auth.uid()) ) );
Postgres auto-filters — wrong rows can’t be returned even if app code has a bug
User can belong to multiple tenants
Best practice: write (SELECT auth.uid()) not auth.uid() — caches the ID once per query instead of re-evaluating per row
Multi-Tenant: Pattern 2
Claim in the login token — tenant_id baked into the JWT at login
CREATE POLICY "view own tenant's notes"ON notesFORSELECTUSING (tenant_id = (auth.jwt()->>'tenant_id')::uuid);
No extra database lookup — reads straight from the login token
Requires an Access Token Hook: a Postgres function that injects tenant_id into the token at login
One tenant per session — user re-logins to switch organisations
Pattern 1
Pattern 2
Setup
Simple, no hooks
Needs an Access Token Hook
Speed
Subquery per query
Single equality check
Multi-org
Yes
One org per session
Auth: Tier & Feature Matrix
Feature
Free
Pro
Team
Enterprise
MAU included
50K
100K
100K
Custom
Custom Access Token Hook
✅
✅
✅
✅
Send SMS / Email Hook
✅
✅
✅
✅
SAML SSO
❌
✅
✅
✅
SSO MAUs included
❌
50
50
Custom
MFA Verification Hook
❌
❌
✅
✅
Dashboard SSO
❌
❌
✅
✅
SOC 2 Type 2
✅
✅
✅
✅
ISO 27001 certificate
❌
❌
✅
✅
Uptime SLA (99.9%)
❌
❌
❌
✅
24/7 Sev-1 support
❌
❌
❌
✅
BYO Cloud
❌
❌
❌
✅
Auth: SAML SSO — Multi-Tenant Pattern
Each enterprise customer gets their own SAML connection. sso_provider_id scopes the JWT.
// Route user to their org's IdP by domainconst { data } =await supabase.auth.signInWithSSO({ domain:'customer-corp.com',})
-- RLS policy using sso_provider_id as the tenant identifierCREATE POLICY "tenant isolation"ON records AS RESTRICTIVEUSING ( provider_id = (auth.jwt() ->'amr'->0->>'provider') );
Auth: Self-Hosting Reality
Managed Cloud
Self-Hosted
Officially supported deployment
✅
Docker Compose only
Kubernetes / Helm
—
Community (supabase-community/supabase-kubernetes)
Support
Enterprise SLA / Team email
Community only (GitHub Discussions, Discord)
Uptime SLA
✅ Enterprise
❌ None
Branching, PITR, metrics, ETL
✅
❌ Unavailable
Data in your own infra
❌ AWS regions
✅
BYO Cloud (Supabase manages in your cloud)
Enterprise (negotiated)
—
Competitive: Auth Vendors — Azure-Native Stacks
Supabase
Auth0
Entra External ID
Cost at <50K MAU
From $25/mo (Pro)
From ~$1,400/mo
$0 (50K MAU free tier)
ANZ data residency
✅ Sydney, all tiers
Enterprise only (AWS AU)
✅ AU Go-Local add-on (paid)
ISO 27001
✅ Team/Enterprise
✅
✅
SAML SSO
Pro+
Enterprise
✅ native
.NET integration
Standard JWKS / AddJwtBearer
SDK
✅ Microsoft.Identity.Web
Open source / self-hostable
✅
❌
❌
Platform beyond auth
✅ Postgres + Storage + Realtime
❌
❌
Reference: Admin Impersonation Pattern
sequenceDiagram
actor A as Admin
participant EF as Edge Function
participant API as Backend APIs
A->>EF: POST /impersonate { target_user_id }
Note over EF: verify admin JWT + is_admin claim
Note over EF: fetch target user via service role,<br/>mint JWT — sub: target · act: admin · exp: +1h
EF-->>A: impersonation token (short-lived, no refresh)
A->>API: request + Bearer impersonation token
Note over API: validates as target user · act claim for audit
Whiteboard Scenarios
Scenario A: Firebase Migration
Most teams migrate incrementally — Auth first (users are portable), then data, then functions.
The biggest conceptual shift is Firestore → Postgres: document collections become relational tables. Everything else maps 1:1.
Scenario A: Product Map
Firebase
Supabase
Firestore (NoSQL docs)
biggest shift →
Postgres (SQL tables)
Firebase Auth
1:1 →
Supabase Auth
Firebase Storage
1:1 →
Supabase Storage
Cloud Functions (HTTP)
→
Edge Functions
Cloud Functions (events)
→
Database Triggers
Cloud Functions (scheduled)
→
pg_cron
FCM (push notifications)
more setup →
Edge Function + DB webhook calls FCM/Expo
Scenario B: Auth + Existing IdP
SAML assertion in — enriched JWT out. The IdP authenticates; Supabase issues the token.
The Access Token Hook runs before the JWT is signed, injecting custom claims (tenant_id, role, plan) without a round-trip to the application.
Scenario B: JWT Flow
sequenceDiagram
actor U as User
participant IdP as Company IdP
participant Auth as Supabase Auth
participant Hook as Access Token Hook
participant DB as Postgres + RLS
U->>IdP: Sign in with company account
IdP-->>Auth: SAML assertion
Auth->>Hook: Raw JWT payload
Hook-->>Auth: + tenant_id, role
Auth-->>U: Signed JWT
U->>DB: API request + JWT
DB->>DB: RLS policy filters rows
DB-->>U: Filtered rows only
Scenario B: BYO Backend
Supabase Auth as a standalone identity layer — any backend validates the JWT via a standard JWKS endpoint.
No Supabase SDK required server-side. RS256-signed JWT; standard library support in .NET, Go, Java, Python, Node.
Scenario B: BYO Backend JWT Flow
sequenceDiagram
actor U as User
participant Auth as Supabase Auth
participant Hook as Access Token Hook
participant API as Customer API (any backend)
participant DB as Customer DB (SQL Server / DB2 / etc.)
U->>Auth: Sign in (email / SAML)
Auth->>Hook: Raw JWT payload
Hook-->>Auth: + tenant_id, role
Auth-->>U: Signed JWT
U->>API: Request + Bearer JWT
API->>API: Validate via JWKS endpoint
API->>DB: Query (tenant-scoped)
DB-->>API: Rows
API-->>U: Response
Scenario C: Multi-Tenant SaaS
Row Level Security enforces tenant isolation at the database layer — authorization that can’t be bypassed by application code.
Two patterns: JWT claim (fast, single-tenant-per-session) or membership table (flexible, multi-org users).
Scenario C: Schema
erDiagram
auth_users {
uuid id PK
text email
}
tenants {
uuid id PK
text name
}
profiles {
uuid id PK
uuid tenant_id FK
text full_name
}
notes {
uuid id PK
uuid user_id FK
uuid tenant_id FK
text content
}
auth_users ||--|| profiles : "trigger on signup"
tenants ||--o{ profiles : "belongs to"
tenants ||--o{ notes : "belongs to"
auth_users ||--o{ notes : "creates"