Copy-paste Claude Code prompts for Supabase — RLS policies, edge functions, Auth helpers, and Postgres functions. Avoids the common 'auth.uid() returns null' trap.
Supabase makes Postgres + auth + edge-compute approachable, but it punishes mistakes loudly — a missing RLS policy means data leakage, not a 500. The prompts below force Claude to default-deny and double-check the auth context.
Add a 'documents' table for user-uploaded files.
Schema:
- id: uuid primary key default uuid_generate_v4()
- user_id: uuid references auth.users(id) on delete cascade, not null
- bucket_path: text not null
- title: text not null
- visibility: text check (visibility in ('private','shared','public')) default 'private'
- created_at: timestamptz default now()
Constraints (RLS):
- ENABLE ROW LEVEL SECURITY on the table.
- Policy 'documents_owner_all' for ALL using (user_id = auth.uid())
with check (user_id = auth.uid()).
- Policy 'documents_public_read' for SELECT to anon, authenticated using
(visibility = 'public').
- Policy 'documents_shared_read' for SELECT to authenticated using
(visibility = 'shared' AND EXISTS (SELECT 1 FROM document_shares ds WHERE
ds.document_id = id AND ds.user_id = auth.uid())).
- DO NOT add a default permissive ALL policy.
Produce two files:
- supabase/migrations/_create_documents.sql (table + indexes + RLS).
- supabase/migrations/_create_document_shares.sql (the join table
for shared visibility, with its own RLS).
Add an edge function 'document-thumbnail' at supabase/functions/
document-thumbnail/index.ts that:
- Reads the authorization header, validates the JWT via
supabaseClient.auth.getUser(jwt) — returns 401 if invalid
- Reads a 'document_id' from the JSON body
- Verifies the user can read the document by selecting from public.documents
(RLS enforces ownership/visibility — if no row, return 404)
- Streams the file from Storage bucket 'documents', resizes via Deno's
imagescript to 256x256 webp, returns the thumbnail bytes
Constraints:
- Deno runtime — use Deno.env.get('SUPABASE_URL'), Deno.env.get(
'SUPABASE_ANON_KEY'). Never service-role keys (the function runs in
user context).
- Use @supabase/supabase-js with the user's JWT, not the service role.
- Response: image/webp, Cache-Control: private, max-age=300.
- Error format: { error: { code, message } } — match the project's
existing error shape in supabase/functions/_shared/errors.ts.
Read every CREATE TABLE in supabase/migrations/. For each public.* table:
- Is RLS enabled? (look for ALTER TABLE ... ENABLE ROW LEVEL SECURITY)
- If yes, are there policies covering each of SELECT/INSERT/UPDATE/DELETE
for the anon and authenticated roles?
- If a role/operation pair has no policy, that operation is denied — note it
as "denied by default: / / " or "MISSING POLICY (risk):
/ / ".
Output a table:
| table | rls_enabled | anon_select | anon_write | auth_select | auth_write | risk |
Risk = "high" if anon can SELECT/UPDATE/DELETE without an explicit policy
restricting it; "low" otherwise. Don't write any migrations yet — I'll triage.
CLAUDE.md for Supabase
## Supabase policy
- RLS on every public.* table. Default-deny.
- Edge functions: Deno, user JWT, never service role unless explicitly required
+ commented at top of file.
- Migrations: numbered timestamp prefixes. Never edit applied migrations.
- DB access from client: only via supabase-js with anon key + user JWT. Never
service role on the client side.
Related: Drizzle prompts.
Frequently asked questions
Will Claude write RLS that actually blocks anonymous reads?
Yes if you remind it: `Default-deny — every public.* table has RLS enabled with no permissive policies for the anon role unless explicitly stated.` The forgotten-RLS-on-new-table is the #1 Supabase production leak.Does Claude understand the auth.uid() return contexts?
Mostly — but it occasionally writes policies using auth.uid() inside a SECURITY DEFINER function, where it returns null. Use the policy prompt below; it includes the trap check.Edge functions in Deno or local Node?
Edge Functions are Deno. Tell Claude `Supabase Edge Functions are Deno runtime — use Deno APIs (Deno.env, std/http), never node:fs.`
Free tools
More examples
Claude API Python QuickstartClaude API Node.js / TypeScript QuickstartClaude API Streaming in PythonClaude API Streaming in Node.js / TypeScriptClaude API Tool Use in PythonClaude API Tool Use in Node.js / TypeScript