VibeRaven
What is the difference between USING and WITH CHECK in a Supabase RLS policy?
using decides which existing rows a command can see or act on, and rows it rejects are skipped without a word. with check decides which new or changed rows may be written, and a row it rejects makes the whole statement fail. select and delete policies take only using, insert takes only with check, and update and all take both, reusing the using expression for new rows when with check is left out.
Why this matters
The two clauses look alike and are easy to mix up. An insert policy written with using is rejected when you create it, because Postgres says "An INSERT policy cannot have a USING expression". An update policy with only using lets a user write any values the using expression allows, so using (true) alone lets anyone rewrite every row. And when with check rejects a row, the message is new row violates row-level security policy, which reads like a missing policy but means the row you sent did not pass.
How to pick the clause for each policy
- Write one policy per command. Supabase's RLS guide does, and says a for all policy "hides which operation each rule was meant to cover".
- select and delete: using only. The expression filters the rows the command sees, and rows that fail it are skipped with no error.
- insert: with check only. The expression is checked against the row being added, and the insert fails if it is false or null.
- update: both. using picks which existing rows can be changed; with check is evaluated against "the proposed new contents of the row, not the original contents". Leave with check out and the using expression is applied to the new row as well.
- update also needs select. Supabase says an update without a matching select policy "will not work as expected", and Postgres applies the select policies whenever an update reads columns, for example in its where clause.
- To see which clause a live policy has, read pg_policies: qual is the using expression and with_check the with check expression. A null with_check on an update or all policy means using is doing both jobs.
select policyname, cmd, qual, with_check from pg_policies where schemaname = 'public' and tablename = 'todos';
One example for each command
Each example is for a todos table whose user_id column holds the owner's id, as in Supabase's RLS guide.
SELECT: using filters what you can read
A row whose using expression is false or null is not returned. No error, just fewer rows.
create policy "Users can view their own todos"
on public.todos for select
to authenticated
using ( (select auth.uid()) = user_id );INSERT: with check validates the new row
The check runs against the row being inserted. If the app sends someone else's user_id, or none, the insert fails with new row violates row-level security policy.
create policy "Users can create their own todos"
on public.todos for insert
to authenticated
with check ( (select auth.uid()) = user_id );UPDATE: using finds the row, with check validates the result
using limits the update to the user's own rows; with check stops them from setting user_id to someone else. A row that fails using is skipped, and a result that fails with check stops the statement.
create policy "Users can update their own todos"
on public.todos for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );DELETE: using decides what can be removed
Rows that fail using are not deleted and nothing is reported, so check the affected row count when a delete seems to do nothing.
create policy "Users can delete their own todos"
on public.todos for delete
to authenticated
using ( (select auth.uid()) = user_id );ALL: one policy for every command
A for all policy applies to select, insert, update and delete. With only a using expression, Postgres uses it for new rows too. It works, but separate policies show what each command is allowed to do.
create policy "Users manage their own todos"
on public.todos for all
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );When either clause is just true
with check (true) on insert lets any signed-in user create rows that belong to anyone, and using (true) on update or delete lets them change or remove every row. The Security Advisor flags always-true conditions, and the using (true) guide below covers when they are fine.
Optional repo check
For AI-built Vercel + Supabase apps, the free Supabase RLS checker (linked below) and npx -y viberaven@1.6.3 check read migration SQL for clauses that are a bare true. In runs on 2026-10-04 and 2026-10-05 the check reported an update policy with using (true) as a critical rls_policy_allows_all_write whether or not it had a with check: with none it lets users "update every row, to any values", and with a real with check it still lets them "update every row, as long as the new values pass its WITH CHECK". It reported a select policy with using (true) as rls_policy_allows_all_read, and an update policy with a real using and with check (true) as a high rls_policy_allows_all_write, because users can "set any values on them, including an owner column such as user_id". Neither tool can tell whether a condition such as user_id = auth.uid() matches the rows your app sends; test that with a signed-in user. It is advice, not a gate, and a repository check, not a live database or security test. It exits with code 1 when it finds a blocker and writes a .viberaven folder that it does not add to .gitignore.
Related guides
- Why is my Supabase RLS policy not working?
- What does using (true) mean in a Supabase RLS policy, and when is it wrong?
- Supabase "new row violates row-level security policy" after deploy: how to diagnose and fix it
- What Is RLS on Supabase? (AI-Built Apps)
- Free Supabase RLS checker
- How do I test Supabase RLS policies?
Sources
Which clause is behind new row violates row-level security policy?
With check, or the lack of any policy that allows the row. The message means a row being inserted or updated did not pass a with check expression (or the using expression an update reuses as one) for the role the request ran as. The RLS policy error guide linked below walks through the full diagnosis after a deploy.
Can a SELECT policy have WITH CHECK?
No. Postgres says "A SELECT policy cannot have a WITH CHECK expression", and a delete policy cannot have one either. The reverse holds for insert: "An INSERT policy cannot have a USING expression".
What happens if an update policy has no WITH CHECK?
Postgres uses the using expression for both jobs: which rows can be updated and which new rows are allowed. That is fine when using checks ownership, and lets any values through when it is true.