Overview

Supabase bundles managed Postgres, Auth, Storage, Realtime, and an auto-generated REST and GraphQL Data API. Use it when you want a Postgres-backed backend with built-in auth and row-level security and no database cluster to run. Because the Data API exposes tables directly, the key model and supabase-rls are the security boundary.

Use publishable and secret keys, not anon and service_role

New projects use sb_publishable_... (low privilege, safe in browser and mobile code) and sb_secret_... (elevated, server only). The legacy JWT-based anon and service_role keys are being deprecated by the end of 2026, and both systems coexist until then. A secret key maps to the service_role Postgres role, which has BYPASSRLS, so it skips every policy.

  • Keep secret keys in server environments only: API handlers, Edge Functions, CI. Supabase returns 401 when a secret key is used from a browser, but never rely on that. See secrets-and-env.
  • Send the new keys in the apikey header. They are not JWTs, so do not verify them as JWTs or put them in Authorization: Bearer.
  • To rotate a leaked key: create a replacement, deploy it, then delete the old key.
// Browser-safe
const supabase = createClient(url, process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!)
// Never: createClient(url, process.env.NEXT_PUBLIC_SUPABASE_SECRET_KEY!)

Grant Data API access explicitly

New tables in the public schema are no longer exposed to the Data API automatically. The change is the default for new projects from 2026-05-30 and is enforced on all existing projects on 2026-10-30; tables that already exist keep their grants. Grant each role what it needs, then enable RLS:

grant select on public.posts to anon;
grant select, insert, update, delete on public.posts to authenticated;
alter table public.posts enable row level security;

Without a GRANT, a role cannot reach the table at all, and a missing grant fails before any policy runs. Add the grants to your migrations so they are reviewed and reproducible. Policy details are in supabase-rls.

npm install -D supabase        # global npm install is unsupported
npx supabase init
npx supabase link              # link to a hosted project
supabase start                 # full local stack in Docker

supabase init creates supabase/ with config.toml, a seed file, and the migrations folder. supabase link is required before pushing migrations. The local stack is ephemeral and reseeds from supabase/seed.sql.

Change the schema through migrations only

supabase migration new add_posts_table   # writes supabase/migrations/<timestamp>_add_posts_table.sql
supabase db push                         # apply pending migrations to the linked project

Dashboard schema edits in production bypass version control. Prototype locally, then capture the change in a migration. See migrations for the general contract.

Pick the connection mode by workload

ModeEndpointUse for
Directdb.<ref>.supabase.co:5432 (IPv6, or IPv4 with the add-on)Persistent servers, migrations
Session pooleraws-<n>-<region>.pooler.supabase.com:5432 (IPv4)Persistent clients that cannot use IPv6
Transaction pooleraws-<n>-<region>.pooler.supabase.com:6543 (IPv4)Serverless and edge functions

Transaction mode shares server connections across clients and does not support prepared statements or query pipelining, so disable both in the driver. Port 6543 serves transaction mode only; session mode on 6543 was deprecated on 2025-02-28. In serverless code, create the client once at module scope and keep its pool to one connection. For Prisma settings see prisma-pooling; for Postgres-side pooling see postgres.