Overview

Supabase is an open-source Firebase alternative that bundles Postgres, Auth, Storage, Realtime, and auto-generated REST and GraphQL APIs into a single managed platform. Use it when you want a Postgres-backed backend with built-in auth and row-level security without operating your own database cluster.

Install the Supabase CLI, then run supabase init in your project root. This creates a supabase/ folder containing config.toml, a seed file, and the migrations directory (Supabase docs).

npm install -g supabase   # or: brew install supabase/tap/supabase
supabase init
supabase link             # links the local project to a hosted Supabase project

supabase link stores a project reference in supabase/.temp/ and is required before you can push migrations remotely.

Use supabase start for local development

supabase start boots a full local stack (Postgres, Auth, Storage, Studio, Realtime) inside Docker (Supabase docs). Run it before writing migrations or testing Edge Functions locally.

supabase start
# Outputs local API URL, anon key, and service_role key for local use only

Stop the stack with supabase stop. The local environment is ephemeral; seed data is reapplied from supabase/seed.sql on each fresh start.

Manage schema changes through migrations, never through the Dashboard in production

Create a migration file with supabase migration new <name>, which writes a timestamped SQL file to supabase/migrations/ (Supabase docs). Edit that file, then apply it.

supabase migration new add_posts_table
# Edit supabase/migrations/<timestamp>_add_posts_table.sql
 
supabase db push           # applies all pending migrations to the linked remote project

Direct Dashboard schema edits in production bypass version control. Use the Dashboard only to prototype locally, then capture the diff in a migration file. See migrations for general migration discipline.

Use transaction mode pooling (port 6543) for serverless workloads

Supabase provides Supavisor as its connection pooler. Serverless functions and edge runtimes open a new TCP connection per invocation; direct Postgres connections (port 5432) will exhaust the connection limit under load (Supabase docs).

Transaction mode (port 6543) shares a fixed pool of server-side connections across all clients. A client holds a connection only for the duration of a single transaction, then releases it. This is the correct mode for Edge Functions, Next.js API routes, and any stateless handler.

Session mode (port 5432) gives each client an exclusive connection for the lifetime of its session. Use it only for long-lived processes such as migration runners or ORMs that rely on prepared statements. Note: as of February 28, 2025, Supabase deprecated session mode on port 6543; port 6543 now exclusively serves transaction mode (Supabase changelog).

# Transaction pooler (serverless)
postgresql://postgres.<project-ref>:<password>@aws-0-<region>.pooler.supabase.com:6543/postgres

# Session / direct (long-lived processes, migrations)
postgresql://postgres.<project-ref>:<password>@aws-0-<region>.pooler.supabase.com:5432/postgres

Transaction mode does not support prepared statements. ORMs that use prepared statements by default (including Prisma) must disable them when using port 6543. See prisma-pooling for the Prisma-specific configuration.

Never expose the service_role key in client-side code

Supabase projects have two primary keys. The anon key (also called the publishable key) is safe to ship in browser or mobile code; access is governed by Postgres supabase-rls policies on the anon and authenticated roles. The service_role key carries the BYPASSRLS attribute, which means it skips every Row Level Security policy and grants full read/write access to all tables (Supabase docs).

The service_role key belongs only in server-side environments: backend API handlers, Edge Functions running in a trusted server context, CI scripts. Store it as an environment variable and never commit it to source control. See secrets-and-env for secret management patterns.

// Safe: anon key in a browser client
const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!)
 
// UNSAFE: service_role key must never appear in NEXT_PUBLIC_ variables
// const admin = createClient(url, process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY!) // do not do this

RLS policy detail lives in supabase-rls. Auth session handling is covered in auth-sessions.