Free · Runs in your browser · 30 seconds
Supabase

Supabase RLS Policy Generator

How do I write a Supabase RLS policy?

Turning Row Level Security on is one line; the hard part is the four policies underneath it, and getting select right while leaving insert wide open is the usual way a table stays exposed. Describe who should reach your table in plain English and copy out the SQL.

Who should reach this table?
Paste into the Supabase SQL editor
-- Row Level Security for public.profiles
-- Enabling RLS denies everything by default; each policy below adds back
-- exactly one allowed operation. Run this in the Supabase SQL editor.

alter table public.profiles enable row level security;

-- Re-running this file should be safe, so drop first.
drop policy if exists "profiles_select" on public.profiles;
drop policy if exists "profiles_insert" on public.profiles;
drop policy if exists "profiles_update" on public.profiles;
drop policy if exists "profiles_delete" on public.profiles;

create policy "profiles_select"
  on public.profiles for select
  to authenticated
  using (auth.uid() = user_id);

create policy "profiles_insert"
  on public.profiles for insert
  to authenticated
  with check (auth.uid() = user_id);

create policy "profiles_update"
  on public.profiles for update
  to authenticated
  using (auth.uid() = user_id)
  with check (auth.uid() = user_id);

create policy "profiles_delete"
  on public.profiles for delete
  to authenticated
  using (auth.uid() = user_id);

-- Verify: this should return zero rows for a signed-out caller.
-- select * from public.profiles;

Generated in your browser - the table name never leaves this page. Read the policy before running it, and re-run the checker afterwards to confirm the table stopped answering.

What this generator produces

Enable Row Level Security

The alter table statement that switches RLS on. On its own it denies everything - each policy below then adds back exactly one allowed operation.

Read more →

Owner-scoped access

Policies matching auth.uid() against your ownership column, so each signed-in user reaches only their own rows. The right default for profiles, orders, and messages.

Public-read tables

For published content: anyone may read, only the owner may write. Read access goes to anon and authenticated, writes stay owner-scoped.

Server-only tables

RLS on and no policies at all, so the anon and authenticated roles get zero rows. Reach it from server code with the service-role key.

Read more →

All four operations covered

Separate select, insert, update, and delete policies, with with-check clauses on the writes so a user cannot insert or update a row into someone else's name.

Safe to re-run

Every policy is dropped before it is created, so pasting the SQL a second time updates your policies instead of erroring.

How the Supabase RLS Policy Generator works

  1. Step 1

    Name your table and owner column

    The table you want protected and the column holding the owner's user id - usually user_id. Nothing is sent anywhere; this runs in your browser.

  2. Step 2

    Say who should reach it

    Owner-only, public read with owner writes, any signed-in user, or nobody from the client at all. Each option explains the kind of table it suits.

  3. Step 3

    Run it, then verify from outside

    Paste the SQL into the Supabase SQL editor and run it. Then check the table from an anonymous request - our RLS checker does exactly that, using the anon key from your own bundle.

Frequently asked questions

How do I write a Supabase RLS policy?

A policy has three parts: the operation it governs (select, insert, update, or delete), the role it applies to (anon or authenticated), and the condition a row must satisfy. Reads use a using clause; writes use with check, which validates the row being written. For per-user ownership the condition is auth.uid() = user_id. You need one policy per operation you want to allow - enabling RLS with no policies denies everything, which is the safe starting point.

What is Row Level Security?

Row Level Security is a Postgres feature that decides, row by row, who may select, insert, update, or delete. Supabase exposes your tables over HTTP through PostgREST, so RLS is the only thing between a table and the public internet - the anon key that reaches it ships in your JavaScript by design. A table with RLS switched off is readable by anyone who opens DevTools and copies that key.

Why do I need separate insert and update policies?

Because a select policy only governs reading. This is the most common way a table stays exposed after someone believes they secured it: they write a policy limiting reads to the owner, see their own rows correctly, and never notice that insert and update were never restricted, so any signed-in user can still write rows attributed to somebody else. Write policies also need a with check clause, which validates the row being written rather than the rows being read.

Is it free? Do I need to sign in?

It is completely free and there is no account, no sign-in, and no limit on how many times you use it. Ubserve makes money from the paid security report, not from this page. The scanner it pairs with is free to run too - you get a grade, an issue count, and up to two findings in full before anything is asked of you.

Does my table name get sent to Ubserve?

Nothing leaves your browser. This generator is plain string assembly running on your own machine: the table name, column name, or header options you choose are never sent to Ubserve or anywhere else, and there is nothing for us to store. You can confirm it by opening DevTools and watching the Network tab while you use it - no request is made.

How do I confirm the policy actually worked?

Test it the way an attacker would, from outside and signed out. Request the table with only your public anon key and confirm nothing comes back - if rows still arrive, the policy is not doing what you think. Reading the policy in the dashboard is not the same test, because a policy can exist and still be permissive. Our free RLS checker runs this against your live app using the anon key from your own bundle.

What founders say

5.0 out of 5 - Ubserve, from 3 reviews

“Helped me find a critical database issue that would've done real damage. I recommend it for every founder.”
Juan
“Caught a critical CSP issue on my frontend within minutes. Don't ship without a scan.”
Zenix
“The free scan took under 60 seconds to find real issues in my project.”
George P.

Check what your live app serves

Anything you check here only covers what you hand it. What an attacker sees is what your deployed app serves, so paste your URL and check that too.

https://

Free, no account needed. We only read what your app already serves publicly.