Builds secure Supabase apps - Row Level Security policies as the authorization layer, schema design against auth.users, Edge Functions for service-role work, realtime subscriptions, and versioned migrations. Use when someone asks "set up RLS for my tables", "is my Supabase app secure", "anyone can read my table with the anon key", "when do I need an Edge Function", or "why is my realtime subscription not receiving rows". Do NOT use for general Postgres schema design without Supabase - use database-schema instead; for tuning slow queries, use sql-query-optimizer; for Stripe billing inside a Supabase app, use stripe-integration.
Click to play with sound.
---
name: Supabase Expert
description: Builds secure Supabase apps - Row Level Security policies as the authorization layer, schema design against auth.users, Edge Functions for service-role work, realtime subscriptions, and versioned migrations. Use when someone asks "set up RLS for my tables", "is my Supabase app secure", "anyone can read my table with the anon key", "when do I need an Edge Function", or "why is my realtime subscription not receiving rows". Do NOT use for general Postgres schema design without Supabase - use database-schema instead; for tuning slow queries, use sql-query-optimizer; for Stripe billing inside a Supabase app, use stripe-integration.
---
# Supabase Expert
Supabase exposes Postgres directly to browsers through the anon key, which means Row Level Security is not a hardening step - it IS the authorization layer. A table without RLS is a table every visitor can read and write with credentials shipped in your JavaScript bundle. This skill builds Supabase apps where every table fails closed, privileged work is isolated server-side, and schema plus policies are versioned artifacts.
## Operating procedure
RLS comes immediately after tables exist because every hour a table lives without policies is an hour it is publicly readable.
### Step 1: Gather inputs
1. The data model and, for every table, its access rule in one sentence: "owners read/write their own rows", "team members read team rows", "public read, owner write". If a table's rule cannot be stated in a sentence, the model is not ready for policies.
2. Auth model - individual users only, or teams/orgs (which need a membership table that policies join through)?
3. Client surfaces - browser, mobile, server? Anything server-side that legitimately needs to bypass RLS?
4. Realtime and Storage requirements, which need their own policy passes.
### Step 2: Design the schema against auth
- Ownership column on every user-owned table: `user_id uuid not null references auth.users(id) default auth.uid()`. The default closes the classic bug where the client forgets to send user_id (or sends someone else's).
- Never modify the `auth` schema itself; a `public.profiles` table (keyed by the auth user id, populated by an on-signup trigger) holds app-level user data.
- Standard schema discipline applies - constraints, correct types, indexed foreign keys. For deep schema questions, pair with database-schema.