Supabase RLS Policy Builder
Write complete Supabase Row Level Security policies for a table from its schema. Generates ENABLE ROW LEVEL SECURITY plus all CRUD policies using the correct auth.uid() patterns, role scoping, and WITH CHECK clauses.
@system/supabase-rls-policy-builder
Supabase RLS Policy Builder
Purpose: Given a table schema and description of who can access what, write the complete set of production-ready Supabase RLS policies. The output is paste-ready SQL — no gaps, no USING (true) shortcuts on sensitive data.
Invocation
/rls-build <table-schema> [access-rules]
Examples:
/rls-build CREATE TABLE skill_reports (id uuid, skill_id uuid, reporter_id uuid, reason text, status text) -- Users can create reports on any active skill; only admins can read all reports; reporters can see their own/rls-build skill_collections table -- users own their collections; collections with is_public=true are readable by anyone
Access Pattern Library
Use these patterns as building blocks. Pick the right one for each operation:
Pattern A: User owns row (most common)
-- User sees only their own rows
CREATE POLICY "[table]_select_own" ON public.[table]
FOR SELECT TO authenticated
USING (auth.uid() = user_id);
-- User can only insert rows they own
CREATE POLICY "[table]_insert_own" ON public.[table]
FOR INSERT TO authenticated
WITH CHECK (auth.uid() = user_id);
-- User can only update their own rows
CREATE POLICY "[table]_update_own" ON public.[table]
FOR UPDATE TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- User can only delete their own rows
CREATE POLICY "[table]_delete_own" ON public.[table]
FOR DELETE TO authenticated
USING (auth.uid() = user_id);
Pattern B: Public read, owner write
-- Anyone (including anon) can read active rows
CREATE POLICY "[table]_select_public" ON public.[table]
FOR SELECT
USING (status = 'active');
-- Only the owner can update/delete
CREATE POLICY "[table]_update_own" ON public.[table]
FOR UPDATE TO authenticated
USING (auth.uid() = author_id)
WITH CHECK (auth.uid() = author_id);
Pattern C: Read own + read public
-- Users see their own rows OR public rows
CREATE POLICY "[table]_select" ON public.[table]
FOR SELECT TO authenticated
USING (auth.uid() = user_id OR is_public = true);
Pattern D: Write to parent-owned resource
Used when a child row's ownership is determined via a parent JOIN (e.g., inserting a comment on a skill you own):
CREATE POLICY "[table]_insert_to_own_skill" ON public.[table]
FOR INSERT TO authenticated
WITH CHECK (
EXISTS (
SELECT 1 FROM public.skills
WHERE id = skill_id AND author_id = auth.uid()
)
);
Pattern E: Authenticated users can create, see their own
Common for reports, notifications, submissions:
CREATE POLICY "[table]_insert" ON public.[table]
FOR INSERT TO authenticated
WITH CHECK (auth.uid() = submitter_id);
CREATE POLICY "[table]_select_own" ON public.[table]
FOR SELECT TO authenticated
USING (auth.uid() = submitter_id);
-- No UPDATE or DELETE — immutable after submission
Pattern F: Service role for server-side operations
Use when the server (not the user) writes to a table:
-- Server can do anything (bypasses RLS via service role key)
CREATE POLICY "[table]_service" ON public.[table]
USING (auth.role() = 'service_role')
WITH CHECK (auth.role() = 'service_role');
-- Document why service role is needed above this policy
Pattern G: Admin-only table
-- Read: only if user is in admins list (example: users.is_admin column)
CREATE POLICY "[table]_admin_select" ON public.[table]
FOR SELECT TO authenticated
USING (
EXISTS (
SELECT 1 FROM public.users
WHERE id = auth.uid() AND is_admin = true
)
);
Full Template (User-Owned Table)
-- RLS for [table]
ALTER TABLE public.[table] ENABLE ROW LEVEL SECURITY;
-- [Brief description of who accesses what]
CREATE POLICY "[table]_select_own" ON public.[table]
FOR SELECT TO authenticated
USING (auth.uid() = user_id);
CREATE POLICY "[table]_insert_own" ON public.[table]
FOR INSERT TO authenticated
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "[table]_update_own" ON public.[table]
FOR UPDATE TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "[table]_delete_own" ON public.[table]
FOR DELETE TO authenticated
USING (auth.uid() = user_id);
Critical Rules
- Never use
USING (true)on INSERT — INSERT policies needWITH CHECK, notUSING - UPDATE requires BOTH
USINGandWITH CHECK—USINGchecks old row,WITH CHECKchecks new row auth.uid()vsauth.role()— uid() returns the user's UUID, role() returns 'anon'/'authenticated'/'service_role'- Don't mix ALL with specific operations — use
FOR SELECT,FOR INSERTetc. individually for clarity - Test anon access — if anon role should NOT see data, add explicit denial or ensure no policy grants anon access
Output Format
- Complete SQL block —
ENABLE ROW LEVEL SECURITY+ all policies - Policy rationale — one-line comment above each policy explaining WHY
- Gaps identified — call out any operations not covered (e.g., "No DELETE policy = users cannot delete their reports")
$20 more to next tier
Created by
Info
Embed
Add this skill card to any webpage.
<iframe src="https://skillslap.com/skill/db97e689-7a3c-4f53-b9c7-9db5abd06b29/embed"
width="400" height="200"
style="border:none;border-radius:12px;"
title="SkillSlap Skill: Supabase RLS Policy Builder">
</iframe>