supabase-expert
Skill1 starsChanged 38 days agoMITView on GitHub
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.
4 other projects use a version of this.
Adopted by
Most recently taken up by
- Vinix24/vnx-orchestrationtheir own version, first seen Nov 6, 2026
- saksham-techlabs/ai-builder-skills-packtheir own version, first seen Oct 25, 2026
- thesaifalitai/claude-setuptheir own version, first seen Feb 27, 2026
- azlekov/my-claude-codetheir own version, first seen Jan 7, 2026
Other versions4 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 4
- Vinix24/vnx-orchestration★ 63same name
- thesaifalitai/claude-setup★ 9same name
- azlekov/my-claude-code★ 1same name
- saksham-techlabs/ai-builder-skills-pack★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository6 other files it gives its agents.
- api-designSkill
- database-schemaSkill
- graphql-schemaSkill
- nextjs-app-routerSkill
- stripe-integrationSkill
- web-performanceSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
14 rules. 0 are common, 11 are distinctive, and 3 are very specific to this project.
0
Common
Most of the 3 edited versions we read keep these.
11
Distinctive
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 26
Never modify the auth schema itself.
- Distinctive: line 50
Default-deny is the safety net: RLS enabled with no policy blocks everything.
- Distinctive: line 58
Use an Edge Function when the operation needs privileges the caller must not hold (admin writes, cross-user aggregation, third-party API …
- Distinctive: line 70
If an operation works under the caller's own RLS, do it from the client - Edge Functions are for privilege boundaries, not a default API …
- Distinctive: line 80
All schema and policy changes go through supabase CLI migrations - the dashboard SQL editor is for exploration, not production change …
- Distinctive: line 81
Test the policy matrix as three actors before shipping: anonymous (no session), user A, and user B attempting to read/write A's rows.
- Distinctive: line 115
Do not leave RLS disabled on any table holding user data - the anon key in the browser reads all of it.
- Distinctive: line 116
Do not ship the service role key anywhere client-side.
- Very specific: line 117
Do not write UPDATE policies with only using - without with check, rows can be rewritten out of the owner's scope.
- Very specific: line 118
Do not rely on client code to set user_id.
- Distinctive: line 119
Do not leave policy predicate columns unindexed - per-row policy checks on unindexed columns collapse query performance.
- Distinctive: line 120
Do not forget that every table in a join needs its own policies, and that storage buckets need policies separate from tables.
- Distinctive: line 124
Every user-data table has RLS enabled, four per-operation policies, and an index on each policy predicate.
- Distinctive: line 127
Every Edge Function validates the caller's JWT before privileged work.
“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
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
- 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.
- Auth model - individual users only, or teams/orgs (which need a membership table that policies join through)?
- Client surfaces - browser, mobile, server? Anything server-side that legitimately needs to bypass RLS?
- 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
authschema itself; apublic.profilestable (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.
Step 3: Enable RLS and write per-operation policies
alter table notes enable row level security;
create policy "owner can read" on notes
for select using (auth.uid() = user_id);
create policy "owner can insert" on notes
for insert with check (auth.uid() = user_id);
create policy "owner can update" on notes
for update using (auth.uid() = user_id)
with check (auth.uid() = user_id);
create policy "owner can delete" on notes
for delete using (auth.uid() = user_id);- One policy per operation (select/insert/update/delete). A single broad
for allpolicy hides asymmetries - reading and deleting rarely deserve identical rules. usingfilters which existing rows the operation can see;with checkvalidates rows being written. UPDATE needs both -usingalone lets a user rewrite their row to belong to someone else.- Default-deny is the safety net: RLS enabled with no policy blocks everything. Add access deliberately, never by disabling RLS "to debug".
- Team access joins through membership:
using (team_id in (select team_id from memberships where user_id = auth.uid())). Wrapauth.uid()calls as(select auth.uid())in policies so Postgres evaluates it once per query instead of once per row. - Index every column policies filter on (
user_id,team_id) - policies execute per row, and an unindexed predicate turns every select into a sequential scan.
Step 4: Isolate privileged work in Edge Functions
The service role key bypasses all RLS. It exists only server-side - in Edge Functions or a trusted backend - and never in any client bundle, mobile app, or public repo.
Use an Edge Function when the operation needs privileges the caller must not hold (admin writes, cross-user aggregation, third-party API calls with secret keys). The function must validate the caller's JWT itself, because it is running with the master key:
Deno.serve(async (req) => {
const jwt = req.headers.get('Authorization')?.replace('Bearer ', '');
const { data: { user }, error } = await anonClient.auth.getUser(jwt);
if (error || !user) return new Response('unauthorized', { status: 401 });
// privileged work with the service-role client, scoped to what `user` may do
return new Response(JSON.stringify({ ok: true }));
});If an operation works under the caller's own RLS, do it from the client - Edge Functions are for privilege boundaries, not a default API layer.
Step 5: Wire realtime and storage
- Enable replication on tables you subscribe to. Realtime respects RLS for Postgres changes - clients only receive rows their policies let them read, so a "subscription receives nothing" bug is usually a policy bug.
- Subscribe with filters (
event,filter=user_id=eq....) to limit payload volume. - Storage buckets have their own RLS-style policies on
storage.objects; a private bucket with no policies is inaccessible, a public one is public to everyone. Set policies per bucket explicitly.
Step 6: Version everything and test as different users
- All schema and policy changes go through supabase CLI migrations - the dashboard SQL editor is for exploration, not production change management.
- Test the policy matrix as three actors before shipping: anonymous (no session), user A, and user B attempting to read/write A's rows. A missing policy fails closed (annoying, safe); a too-broad policy leaks silently (invisible until breach).
Worked contrast
Bad - the table every Supabase security incident starts with:
create table notes (
id uuid default gen_random_uuid() primary key,
user_id uuid, -- nullable, client-supplied
body text
);
-- RLS never enabled: anon key reads and writes every rowGood:
create table notes (
id uuid default gen_random_uuid() primary key,
user_id uuid not null references auth.users(id) default auth.uid(),
body text not null
);
alter table notes enable row level security;
create index on notes (user_id); -- policy predicate is indexed
-- plus the four per-operation policies from Step 3Deliverable
Produce a Supabase security and schema package containing: the migration files for tables with ownership columns and indexes on policy predicates; per-operation RLS policies for every table plus storage bucket policies; the Edge Function code for each privilege boundary with JWT validation; and the three-actor test matrix (anon / user A / user B) with observed results per table.
Do NOT
- Do not leave RLS disabled on any table holding user data - the anon key in the browser reads all of it.
- Do not ship the service role key anywhere client-side; it bypasses every policy.
- Do not write UPDATE policies with only
using- withoutwith check, rows can be rewritten out of the owner's scope. - Do not rely on client code to set
user_id; default it fromauth.uid()and constrain it NOT NULL. - Do not leave policy predicate columns unindexed - per-row policy checks on unindexed columns collapse query performance.
- Do not forget that every table in a join needs its own policies, and that storage buckets need policies separate from tables.
Quality bar
- Every user-data table has RLS enabled, four per-operation policies, and an index on each policy predicate.
- The three-actor test passes: anon sees nothing private, user B cannot read or write user A's rows.
- Service role key appears in zero client bundles (grep the build output, not just the source).
- Every Edge Function validates the caller's JWT before privileged work.
- Schema and policies exist as CLI migrations reproducible on a fresh project.
Escalation
Route pure schema modeling to database-schema, slow-query work to sql-query-optimizer, and billing flows to stripe-integration. For a full pre-launch security pass beyond RLS (secrets, dependencies, headers), pair with secure-code-review.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

