Supabase
When your agent starts coding, you gotta let it cook
npx -y skills add ndisisnd/cook --skill supabaseAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Supabase platform standards — Row-Level Security, publishable/anon and secret/service_role key boundaries, Postgres and Edge functions, Storage, Realtime, and the CLI migration workflow. Use when working with RLS policies, Supabase clients, Edge Functions, or supabase/ migrations. Loads alongside the database (Postgres) domain.
SKILL.md
8.2 KB, as published. Nobody here has run it
Supabase Standards
Supabase is Postgres underneath, so this domain co-loads with database (generic
Postgres) and the auth / security concerns. It owns only the platform
contract: RLS, the key boundary, Postgres/Edge function security, and the CLI
migration workflow. Generic Postgres rules (types, constraints, indexing-in-general,
expand-contract, N+1, transactions) live in database; auth flows (JWT verification,
OAuth, password reset) live in global/refs/auth.md — this domain links to them
rather than restating them.
Priority: P0 — Row-Level Security
- RLS is OFF by default. Every table reachable through the API/PostgREST must
ENABLE ROW LEVEL SECURITYin the same migration that creates it. A table left unguarded behind a publicanonkey is a full data leak. Signal: acreate tableinsupabase/migrationswith no matchingalter table … enable row level security. - RLS-on denies by default — write an explicit policy per operation
(
select/insert/update/delete). Don't rely on onefor allpolicy where operations need different predicates. Signal: a table with RLS enabled and no policy, or a blanketfor all using (true). - Pair every
UPDATE/DELETEpolicy with aSELECTpolicy. Postgres must read the existing row to evaluate theUSINGclause; withoutSELECTthe row is invisible and the write silently affects nothing. Signal: anupdate/deletepolicy on a table with noselectpolicy. - Use
WITH CHECKonINSERT/UPDATEso a user can't write a row they couldn't own or read (e.g. inserting someone else'suser_id). Signal: aninsert/updatepolicy withusingbut nowith check. - Wrap auth calls as
(select auth.uid())/(select auth.jwt())in policy predicates so the planner caches the result per-statement instead of re-evaluating per row. Signal: a bareauth.uid()in a policy on a table that gets scanned. - Never base a policy on
auth.jwt() -> 'user_metadata'—user_metadatais editable by the authenticated user. Useapp_metadata(server-controlled) or a roles table joined via asecurity definerhelper. Signal: a policy readinguser_metadatafor an authorization decision. - Index every column referenced in an RLS predicate (
user_id,tenant_id, …). RLS turns these into per-query filters; an unindexed predicate column is a full scan on every request. (Sharpens thedatabaseindexing rule.) Signal: an RLS predicate column absent from any index.
Priority: P0 — Keys & Client Boundary
- Use publishable keys (
sb_publishable_...) for public clients; treat legacyanonas the compatibility form. Public clients are only safe when RLS protects every exposed table for theanon/authenticatedroles. Signal: a publishable or legacyanonclient used against a table with RLS disabled. - Secret keys (
sb_secret_...) and legacyservice_rolekeys bypass RLS — backend / Edge Functions only. Never ship them to a browser or mobile bundle, never put them in a client-public env (NEXT_PUBLIC_*,EXPO_PUBLIC_*,VITE_*), and never pass them in URLs or query params. A leaked secret key is root on the database. Signal:sb_secret,SUPABASE_SECRET_KEYS,service_role, orSUPABASE_SERVICE_ROLE_KEYreferenced in client-bundled code, a public-prefixed env var, a URL/query param, or unsanitized logs. - A user-session client and an admin client are separate instances. A client
carrying a user session sends the user JWT (RLS applies); do not attach a user
session to the admin client, and the user session must not override the admin API
key. In SSR, build a dedicated admin client from a secret key / legacy
service_rolekey. Signal: one shared client mixing a user session withsb_secret/service_role.
Priority: P0 — Postgres & Edge Functions
- Prefer
SECURITY INVOKER(the default) for Postgres functions. If a function must beSECURITY DEFINER, setsearch_path = '', schema-qualify every relation (public.table), and never create it in an API-exposed schema. Signal:security definerwith noset search_path = '', or such a function in an exposed schema. - Edge Functions are publicly invokable by default — match
verify_jwtto the caller credential and verify inside the handler when needed. Keepverify_jwton for user-JWT calls. Turn it off for webhooks or API-key service calls, then verify the provider signature orapikeyheader in code. Publishable/secret keys are not JWTs and must not be sent asAuthorization: Bearer .... Signal: an Edge Function reading user data withverify_jwt = falseand no signature /apikey/ authorization check; a publishable or secret key sent as a bearer token; a literal key in function source. - Treat Postgres as a pooled remote from Edge Functions — use the connection
pooler / serverless-friendly client; don't open a fresh direct connection per
invocation.
Signal: a
new Pool/ direct-connect per request in a function.
Priority: P1 — Migrations & Workflow
- All schema and RLS changes go through Supabase CLI migrations in
supabase/migrations, version-controlled — never schema-edit only in the dashboard (silent drift). Write RLS policies as explicit SQL; ORM-generated migrations don't capture them. Signal: a dashboard-only schema change with no migration file. - Storage buckets are private by default; gate access with storage RLS policies.
Signal: a
public = truebucket holding user/private data. - P1 (design): enable Realtime per-table deliberately, and remember RLS applies to
Realtime too — a
postgres_changessubscription only emits rows the subscriber can read.
Anti-Patterns
- Table exposed with RLS disabled
- RLS on with no policy, or a blanket
for all using (true) update/deletepolicy without aselectpolicyinsert/updatepolicy withoutwith check- bare
auth.uid()per-row in a policy - policy keyed on
user_metadatafor authorization - unindexed RLS predicate column
sb_secret/service_rolekey in a client bundle, public-prefixed env var, URL, or log- user session attached to the admin client
security definerwith nosearch_path = '', or in an exposed schema- Edge Function on user data with
verify_jwtoff and no in-code check - hardcoded secrets in Edge Function source
- dashboard-only schema edits (drift)
- public storage buckets holding private data
References
Load only what the task requires:
- rls — enable-RLS migration pattern, per-operation policies,
SELECT+UPDATEpairing,WITH CHECK,(select auth.uid())wrapping,app_metadatavsuser_metadata, indexing predicate columns - keys-and-clients — publishable/anon vs secret/service_role, browser/mobile boundary, SSR admin-client separation, public-env pitfalls
- database-functions —
SECURITY INVOKERdefault,SECURITY DEFINER+search_path = ''+ schema qualification, exposed-schema rule,auth.uid()in helpers - edge-functions — Deno runtime, user JWT vs API-key auth,
verify_jwt/ in-code auth, project secrets, connection pooling for Postgres - migrations — CLI workflow, RLS-as-SQL, dashboard drift, storage and Realtime policies
- checklist — pre-deploy review checklist