Supabase vs Firebase vs PocketBase 2026: Real Comparison for Production Apps
Every new project forces the same question: do I spin up my own Postgres + auth + storage, or reach for a BaaS platform? In 2026, three options dominate the serious consideration list. This is a no-fluff technical comparison based on real production use.
The One-Line Summary
- Firebase — mature, battle-tested, Google-backed, proprietary lock-in, expensive at scale
- Supabase — open-source, PostgreSQL underneath, can self-host, growing fast
- PocketBase — single Go binary, unbeatable for small apps and prototypes, limited at scale
Database Model: Where They Differ Most
Firebase (Firestore)
NoSQL document database. Great for hierarchical, user-scoped data with realtime subscriptions:
// Firestore: nested collections
const orderRef = db.collection('users').doc(userId)
.collection('orders').doc(orderId);
await orderRef.set({
items: [{ productId: 'abc', qty: 2 }],
total: 4500,
createdAt: serverTimestamp(),
});
// Realtime listener
onSnapshot(orderRef, (doc) => {
console.log('Order updated:', doc.data());
});
The problem: no JOIN. If you need "all orders with product details", you fetch orders, then fan out N product reads. At 1000 orders = 1001 reads. Costs money and latency.
Supabase (PostgreSQL)
Full relational SQL. Every feature you know from Postgres — JOINs, CTEs, views, stored procedures, full-text search, PostGIS:
// Supabase: proper relational query
const { data, error } = await supabase
.from('orders')
.select(`
id,
total,
created_at,
order_items (
quantity,
products ( name, price, sku )
)
`)
.eq('user_id', userId)
.order('created_at', { ascending: false })
.limit(20);
One query. Server-side JOIN. This alone often decides the choice.
PocketBase
SQLite with a REST/realtime API. The entire schema + auth + storage in one file:
// PocketBase: SDK call
const records = await pb.collection('orders').getList(1, 20, {
filter: `user = "${userId}"`,
expand: 'items,items.product',
sort: '-created',
});
SQLite means single-writer limitation. Fine for low-concurrency apps. Not fine for high-write workloads.
Authentication Comparison
All three support email/password, OAuth (Google, GitHub, Apple), and magic links. The differences are in the details.
Firebase Auth
import { getAuth, signInWithPopup, GoogleAuthProvider } from 'firebase/auth';
const auth = getAuth();
const provider = new GoogleAuthProvider();
const result = await signInWithPopup(auth, provider);
const idToken = await result.user.getIdToken();
// Send idToken to your backend for verification
Custom claims for RBAC:
// Cloud Function (server-side):
await admin.auth().setCustomUserClaims(uid, { role: 'admin', orgId: 'xyz' });
Well-documented, works reliably. But JWT verification requires Firebase Admin SDK — you can't just verify the token with standard JWT libraries without extra work.
Supabase Auth
JWT-based, Row Level Security (RLS) integrates directly with auth:
-- Policy: users can only see their own orders
CREATE POLICY "Users see own orders"
ON orders FOR SELECT
USING (auth.uid() = user_id);
This is a killer feature. Security enforced at the database level — impossible to forget in your API code.
// Check auth status
const { data: { user } } = await supabase.auth.getUser();
// RLS automatically applied — this query only returns YOUR orders
const { data: orders } = await supabase.from('orders').select('*');
PocketBase Auth
Simple and functional. Email/password, OAuth2, OTP. Custom auth fields:
const authData = await pb.collection('users').authWithPassword(
'user@example.com',
'password123'
);
// Token stored in pb.authStore automatically
Realtime Subscriptions
Firebase
Realtime Database / Firestore — built for realtime from day one:
// Firestore realtime
const unsubscribe = onSnapshot(
query(collection(db, 'messages'), where('chatId', '==', chatId)),
(snapshot) => {
snapshot.docChanges().forEach(change => {
if (change.type === 'added') addMessage(change.doc.data());
if (change.type === 'modified') updateMessage(change.doc.data());
});
}
);
Supabase Realtime
Postgres changes via logical replication → WebSocket. Works but has a wrinkle: requires REPLICA IDENTITY FULL for UPDATE/DELETE payloads:
const channel = supabase
.channel('orders-changes')
.on(
'postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'orders', filter: `user_id=eq.${userId}` },
(payload) => console.log('New order:', payload.new)
)
.subscribe();
Also supports Broadcast (arbitrary messages between clients) and Presence (who's online), which Firebase doesn't have natively.
PocketBase
SSE-based realtime, simple API:
pb.collection('orders').subscribe('*', function (e) {
console.log(e.action, e.record);
});
Lightweight, works well for small user counts.
Pricing at Scale: The Real Numbers
| Monthly Active Users | Firebase Cost | Supabase Cost | PocketBase Cost |
|---|---|---|---|
| 10,000 | Free | Free | Self-hosted (~$5/mo VPS) |
| 100,000 | ~$50-200 | $25/mo (Pro) | Self-hosted (~$20/mo VPS) |
| 1,000,000 | ~$2,000-8,000 | ~$599+ (custom) | Self-hosted (~$100/mo) |
| 10,000,000 | ~$20,000-80,000 | Enterprise | Self-hosted scale-up |
Firebase pricing is read/write/storage based. 10M document reads = $3. At scale, this adds up fast. A single poorly optimized query can multiply costs 100x.
Supabase charges by compute/bandwidth/storage, similar to a managed Postgres host. More predictable.
PocketBase: you pay for the VPS. Linear scaling with your infrastructure.
Self-Hosting
| Feature | Firebase | Supabase | PocketBase |
|---|---|---|---|
| Self-host option | No | Yes (complex) | Yes (trivial) |
| Vendor lock-in | High | Low | Zero |
| Data portability | Export JSON | Full Postgres dump | SQLite file |
Supabase self-hosting requires Docker Compose with ~6 containers (Kong, GoTrue, PostgREST, Realtime, Storage, Meta). Works but is not "trivial":
# docker-compose.yml (simplified)
services:
db:
image: supabase/postgres:15.1.0.54
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
auth:
image: supabase/gotrue:v2.132.3
environment:
GOTRUE_DB_DRIVER: postgres
DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@db:5432/postgres
rest:
image: postgrest/postgrest:v12.0.2
environment:
PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@db:5432/postgres
PocketBase self-hosting: download binary, run it. That's it:
./pocketbase serve --http="0.0.0.0:8090"
When to Choose What
Choose Firebase when:
- You're already in the Google Cloud ecosystem
- Your data model is genuinely document-oriented (not relational)
- You need battle-tested realtime at massive scale
- Your team has existing Firebase expertise
Choose Supabase when:
- You have relational data (any app with users, orders, products — that's you)
- You value open source and data ownership
- You want Row Level Security (auth + database security in one place)
- You might self-host in the future
- You're comfortable with SQL
Choose PocketBase when:
- Prototype, MVP, or internal tool
- Solo developer or tiny team
- Traffic is modest (<10k concurrent users)
- You want zero infra complexity
- Budget is the primary constraint
The Verdict for 2026
For most web and mobile apps with relational data: Supabase. The combination of PostgreSQL, RLS, and realtime is hard to beat. The open-source model means you're never held hostage.
For established teams building something guaranteed to scale to millions with a Google Cloud budget: Firebase is still the most mature option.
For hackathons, MVPs, and small production apps: PocketBase is genuinely remarkable — one binary, full stack.
Aunimeda builds production web and mobile apps on modern BaaS infrastructure. Let's discuss your project.
See also: tRPC + Zod type safety, Next.js 15 App Router deep dive