Tutorials Search / Shipping & infrastructure / Pick a BaaS: Firebase vs Supabase
📝 Written ● Beginner Updated 2026-05-13

Pick a BaaS: Firebase vs Supabase

Backend-as-a-Service is a bundle: auth, database, storage, functions, realtime, all behind one SDK. You're not buying a database; you're buying the right to never think about how it's hosted. Firebase and Supabase are the two defaults — different data models, different exit costs, similar pricing curves. This is how you pick.

"BaaS" — backend-as-a-service — names a category that's actually a bet about how applications get built. The bet: a typical app needs auth, a database, file uploads, and a way for those to talk to each other over the network. The traditional path was to assemble that yourself — Postgres for data, Passport / NextAuth for auth, S3 for storage, an API server for glue, and three or four config files keeping them aligned. The BaaS bet is that you can ship the same product faster by accepting one vendor's pre-assembled version of all four. You write less infrastructure code; you get auth that just works; the SDK is what you import. The trade is that the vendor owns the data model and the API shape.

Firebase (Google, since 2011, acquired in 2014) is the older option. Its database is Firestore, a NoSQL document store with realtime sync built in — you write JSON-shaped documents into collections, the SDK pushes updates to every client viewing them. Supabase (independent, since 2020) is "Firebase but Postgres" — same auth + storage + realtime + functions wrapping, but the database is real Postgres with SQL, joins, and foreign keys. The difference looks small but it's actually the most important call you make on this stack: Firestore optimizes for "show me documents that match this filter, in realtime, on millions of clients"; Postgres optimizes for "let me ask any question I think of later." Pick the data model that matches how you'll write your queries.

This tutorial covers the decision (most of the work), the dimensions that actually matter (most lists miss them), and a working "read a row + check who's signed in" hello-world for each. After this, you'll know which to pick and you'll have proven the basics work for that choice. Going from hello-world to a real app is then just more of the same SDK.

What you'll learn

Prerequisites: A frontend framework you're comfortable in (React, Vue, Svelte, vanilla — both SDKs support all of them), a Google account if trying Firebase, a GitHub or email signup if trying Supabase. The hello-worlds run locally; no deploy needed.

Step 1: What you're actually buying

1

Five services wrapped in one SDK

Both Firebase and Supabase bundle the same five capabilities behind one SDK and one set of credentials:

  • Authentication — email/password, Google / Apple / GitHub OAuth, magic links, phone OTP. The SDK gives you "is the user signed in" as a one-line check.
  • Database — where your app's data lives. Firebase = Firestore (NoSQL documents); Supabase = Postgres (relational rows).
  • Storage — file uploads. Profile photos, attachments, user-generated images. Both expose a bucket-shaped API; Firebase backs it with Google Cloud Storage, Supabase with S3.
  • Realtime — push-based updates to clients on data change. The "live counter that updates everywhere" demo. Firestore has it everywhere by default; Supabase has it on tables you opt in.
  • Functions — server-side code triggered on events or HTTP. Firebase Cloud Functions (Node) and Supabase Edge Functions (Deno).

The thing you're paying for, beyond hosting, is that all five are wired to the same user identity. A signed-in user's photo upload (storage) is allowed because of a row-level rule (database) referring to their auth token (authentication). Rolling this yourself means writing that wiring; with a BaaS, the SDK hands you the wired-together version.

Step 2: The data-model question (this is the decision)

2

Firestore optimizes for one shape; Postgres optimizes for change

The single most important difference between Firebase and Supabase is the database. Pick this badly and every later feature is a fight with the schema.

Firestore (Firebase) — NoSQL documents. Data lives in collections of documents; a document is JSON. There are no joins. There are no foreign keys. There are no migrations — schemas exist only in your code. Queries are simple ("documents in this collection where field X equals Y") and very fast at scale, but anything multi-table-ish (think SQL joins) has to be either denormalized into the document or done client-side by fetching multiple collections. Realtime sync is everywhere by default; this is what Firebase is famous for.

Postgres (Supabase) — relational rows. Data lives in tables with typed columns and a schema you define up front. Joins exist. Foreign keys exist. SQL is the query language; the Supabase SDK wraps a query builder over it, but you can also write raw SQL when you need to. Schema changes are migrations — text files in your repo that move the database from one shape to another. Realtime is opt-in per table.

The rule of thumb that holds up surprisingly well: if your app feels like "a feed of posts with comments and likes," Firestore is fine. If it feels like "users, projects, tasks, assignments, with reports across all of them," you'll fight Firestore and Postgres wins. Most real consumer apps drift toward the second shape over a year of iteration, even if they started looking like the first. That bias is the main reason teams pick Supabase as a default in 2026.

Step 3: The dimensions that matter past the first week

3

What both SDK tours don't show you

DimensionFirebaseSupabase
Query power Limited: filter on one field, equality + range on one indexed field. No JOIN, no GROUP BY. Full SQL. Joins, window functions, recursive CTEs, full-text search.
Schema changes None. Add fields whenever; data lives or dies based on app code. Migrations (text files in repo, applied to the DB). More work, more safety.
Auth providers Email, phone, Google, Apple, Facebook, Twitter, GitHub, anonymous, custom JWT. Email/password, magic links, Google, Apple, GitHub, GitLab, Bitbucket, Slack, Discord, Twitter, Microsoft, Notion, Spotify, Twitch, anonymous, phone, SAML.
Realtime Default for all reads. The product's centerpiece. Opt-in per table; uses Postgres logical replication under the hood. Solid but not the default.
Row-level security Firestore Security Rules — a small DSL. Powerful but proprietary. Postgres RLS — SQL. Powerful, portable, the standard.
Exit cost High. The data lives in Firestore's proprietary format; export tools exist but rebuilding the app on another stack means rewriting most queries. Low-to-medium. Data is Postgres — pg_dump and restore anywhere. Auth and storage are vendor-specific but the database, which usually holds the leverage, is portable.
Local dev Firebase Emulator Suite — runs Firestore, Auth, Functions locally. Mature, well-documented. Supabase CLI — runs Postgres + Auth + Storage + Studio in Docker locally. Mature, well-documented.
Self-hosting No. Firebase is a Google-only service. Yes. Supabase is open source; you can run the entire stack on your own Postgres + auth server. Most don't, but you can.
Vendor lock-in (subjective) Significant. The mental model and query shape don't transfer. Modest. Postgres skills and SQL transfer; Supabase-specific helpers don't.

The exit-cost row matters more than people expect. Apps that succeed grow into needs that the original BaaS choice didn't anticipate. With Supabase, "I need to move off" is "pg_dump then run the app against a different Postgres." With Firebase, it's a project measured in months.

Step 4: Pricing — the curve, not the headline

4

Both are free until they aren't; understand the cliff

Both have generous free tiers that cover personal projects and small SaaS. Both jump to a paid plan when you exceed quotas. Both then meter usage on top of the base plan.

Firebase (Spark → Blaze). The Spark plan is free and includes 1 GiB Firestore, 1 GB storage, 10 GB transfer, 50K daily Firestore reads, 20K writes. Past that, you switch to Blaze (pay-as-you-go): $0.06 per 100K Firestore reads, $0.18 per 100K writes, $0.026/GB-month storage. The infamous "Firebase bill" stories are usually a misconfigured client doing infinite reads in a loop on Blaze — the meter doesn't stop. Set a daily budget alert on the GCP project before going to production.

Supabase Free → Pro. Free includes a 500 MB Postgres database, 1 GB storage, 50K monthly active users, 5 GB egress. Pro is a flat $25/month plus usage — 8 GB DB, 100 GB storage, 100K MAU, 250 GB egress. Above Pro is enterprise pricing. The pricing is more "subscription-shaped" than Firebase's pure usage meter; bills don't accidentally explode but plan changes have to be deliberate.

For a real app with ~10K monthly active users and moderate data: both run $25–60/month at the entry paid plans. The curves diverge above that — Firebase scales smoothly with traffic; Supabase has step-changes at plan boundaries (Pro → Team → Enterprise).

Firebase's runaway-bill problem is real. Because Blaze meters every operation and there's no spending cap by default, a buggy client that re-reads in an infinite loop can rack up four-digit bills overnight. Mitigations: set a budget alert at $5 / $20 / $100 in the GCP Billing console, write Firestore Security Rules that limit reads per user, and never deploy a brand-new client without rate limits. Google will sometimes refund the worst cases but you can't count on it.

Step 5: Hello-world — Firebase

5

Sign in with Google, read one document

One-time setup: Firebase consoleAdd project. Inside: Build → Authentication → enable Google. Build → Firestore Database → create in test mode. From the project settings, copy the web config object (an apiKey and a handful of IDs).

In your project:

npm i firebase

Create src/firebase.js:

import { initializeApp } from "firebase/app";
import { getAuth, GoogleAuthProvider, signInWithPopup } from "firebase/auth";
import { getFirestore, doc, getDoc } from "firebase/firestore";

const app = initializeApp({
  apiKey: "AIza...",
  authDomain: "your-project.firebaseapp.com",
  projectId: "your-project",
  // ...rest of config
});

export const auth = getAuth(app);
export const db = getFirestore(app);
export const signInWithGoogle = () =>
  signInWithPopup(auth, new GoogleAuthProvider());

export async function getProfile(uid) {
  const snap = await getDoc(doc(db, "profiles", uid));
  return snap.exists() ? snap.data() : null;
}

In your component:

import { useEffect, useState } from "react";
import { auth, signInWithGoogle, getProfile } from "./firebase";
import { onAuthStateChanged } from "firebase/auth";

export default function App() {
  const [user, setUser] = useState(null);
  const [profile, setProfile] = useState(null);

  useEffect(() => onAuthStateChanged(auth, async (u) => {
    setUser(u);
    if (u) setProfile(await getProfile(u.uid));
  }), []);

  if (!user) return <button onClick={signInWithGoogle}>Sign in</button>;
  return <div>Hi {user.displayName} — profile: {JSON.stringify(profile)}</div>;
}

Run it. Click Sign in. The button opens Google's OAuth popup; you grant consent; the page now greets you by name. Drop a document at profiles/<your-uid> from the Firestore console; refresh; it shows up. That's the bundle in 30 lines.

Step 6: Hello-world — Supabase

6

Same thing, Postgres-shaped

One-time setup: Supabase dashboardNew project. Wait ~90 seconds for the Postgres instance. In Authentication → Providers, enable Google (paste a Google OAuth client ID + secret from GCP). In SQL Editor, create a profiles table:

create table profiles (
  id uuid primary key references auth.users on delete cascade,
  display_name text,
  created_at timestamptz default now()
);
alter table profiles enable row level security;
create policy "own profile" on profiles for select using (auth.uid() = id);

From the project settings, copy the SUPABASE_URL and SUPABASE_ANON_KEY.

In your project:

npm i @supabase/supabase-js

Create src/supabase.js:

import { createClient } from "@supabase/supabase-js";

export const supabase = createClient(
  "https://<PROJECT>.supabase.co",
  "eyJ...ANON_KEY"
);

export const signInWithGoogle = () =>
  supabase.auth.signInWithOAuth({ provider: "google" });

export async function getProfile(userId) {
  const { data, error } = await supabase
    .from("profiles")
    .select("*")
    .eq("id", userId)
    .single();
  return error ? null : data;
}

In your component:

import { useEffect, useState } from "react";
import { supabase, signInWithGoogle, getProfile } from "./supabase";

export default function App() {
  const [user, setUser] = useState(null);
  const [profile, setProfile] = useState(null);

  useEffect(() => {
    supabase.auth.getSession().then(({ data }) => setUser(data.session?.user ?? null));
    const { data: sub } = supabase.auth.onAuthStateChange(async (_e, session) => {
      const u = session?.user ?? null;
      setUser(u);
      if (u) setProfile(await getProfile(u.id));
    });
    return () => sub.subscription.unsubscribe();
  }, []);

  if (!user) return <button onClick={signInWithGoogle}>Sign in</button>;
  return <div>Hi {user.email} — profile: {JSON.stringify(profile)}</div>;
}

Run it. Same flow: click Sign in, Google OAuth popup, page greets you. Insert a row into profiles with your auth.users.id as the primary key; refresh; it appears. The shape is nearly identical to Firebase; the database underneath is Postgres.

Step 7: How to decide between them

7

Three short answers covering most cases

  • Picking right now, don't know yet what the app becomes: Supabase. The exit cost is lower, the data model handles more shapes, and the SQL skills are universal.
  • Your app is fundamentally a realtime sync product (chat, collaborative editor, live game state, multi-device presence): Firebase. Firestore's realtime-everywhere model is genuinely better than Supabase's opt-in realtime for this case.
  • Your app needs joins, reports, or analytics on the data: Supabase. Fighting Firestore on this is a multi-month project; Postgres handles it as a one-line SQL query.

"Both" is rare but legitimate — use Firebase Auth + Firestore for the realtime side of your app and Supabase Postgres for the relational side. Most teams find this is more complexity than it's worth and pick one.

When the right answer is neither

You don't need realtime, you don't need auth-as-a-service, your data is simple. A Vercel-deployed Next.js app talking to Neon Postgres + NextAuth is dramatically simpler than either BaaS. The "all five capabilities in one SDK" framing assumes you need them all; you often don't.

You're already deep on AWS or Azure. AWS Cognito + RDS + S3 (or Azure AD B2C + Cosmos + Blob) does the same things with provider integration you already have. Adding Firebase or Supabase means a second vendor bill and a second outage radius.

You can't accept the data-residency or compliance posture of either. Firebase is Google Cloud; Supabase Cloud is AWS (multi-region available on Team+). Neither is the right answer for a strict on-prem requirement; self-hosted Supabase or a Postgres-on-your-own-server pattern (see Install Postgres and Redis) is.

What's next