
Supabase RLS: `USING` vs `WITH CHECK` Explained with Examples
Clear examples showing how USING and WITH CHECK answer different Supabase RLS policy questions.
Supabase Row Level Security is powerful, but two keywords confuse a lot of developers:
USING
WITH CHECKThey look similar.
They often contain the same condition.
But they are not the same.
This difference matters when your app starts throwing errors like:
new row violates row-level security policyThe mental model
Think of RLS as two questions.
USING
Which existing rows can this user see or touch?WITH CHECK
What new or updated rows is this user allowed to create?That is the core difference.
Example: user-owned projects
Suppose you have a table:
create table projects (
id uuid primary key default gen_random_uuid(),
user_id uuid not null,
name text not null,
created_at timestamptz default now()
);Each project belongs to one authenticated user.
You want users to:
- create their own projects
- read their own projects
- update their own projects
- never access someone else's projects
SELECT uses USING
For reading existing rows:
create policy "Users can read their own projects"
on projects
for select
to authenticated
using (
auth.uid() = user_id
);This policy filters the existing rows the user can see.
If the row belongs to another user, it is invisible.
INSERT uses WITH CHECK
For creating new rows:
create policy "Users can insert their own projects"
on projects
for insert
to authenticated
with check (
auth.uid() = user_id
);This policy checks the row being inserted.
If a user tries to insert:
{
"user_id": "someone-else-id",
"name": "Wrong owner"
}Supabase rejects it.
That is the right behavior.
UPDATE often uses both
For updates, you usually want both:
create policy "Users can update their own projects"
on projects
for update
to authenticated
using (
auth.uid() = user_id
)
with check (
auth.uid() = user_id
);Why both?
USING says:
The user may update rows they currently own.
WITH CHECK says:
After the update, the row must still belong to them.
Without WITH CHECK, you can accidentally allow ownership changes.
DELETE uses USING
For deleting existing rows:
create policy "Users can delete their own projects"
on projects
for delete
to authenticated
using (
auth.uid() = user_id
);There is no new row to validate, so WITH CHECK is not needed.
Summary table
| Operation | Usually needs USING | Usually needs WITH CHECK |
|---|---|---|
| SELECT | Yes | No |
| INSERT | No | Yes |
| UPDATE | Yes | Yes |
| DELETE | Yes | No |
This is not every possible case, but it is a useful default model.
Why AI-generated fixes can be risky
When developers paste an RLS error into an AI assistant, they often get a policy that looks plausible but is too broad.
For example:
create policy "Allow authenticated users"
on projects
for all
to authenticated
using (true)
with check (true);This may make the app work.
It may also allow every signed-in user to access every row.
That is not a fix. That is a bypass.
A better approach
When writing RLS policies, avoid starting with:
How do I make this query pass?Start with:
Who should be allowed to do this, and why?Then translate that into:
USINGfor existing rowsWITH CHECKfor new or changed rows
A tool I made for this
I built FixRLS because I kept seeing developers get stuck between Supabase docs, AI-generated SQL, and vague RLS errors.
FixRLS helps generate policy starting points, AI repair prompts, and proof-of-fix tests for common Supabase patterns.
It does not connect to your database and does not ask for keys.
Try it here:
https://fixrls.dev/new-row-violates-row-level-security-policy
Final thought
USING and WITH CHECK are not just syntax details.
They represent two different security questions:
- Can the user access this existing row?
- Can the user create or change a row into this state?
Once you separate those two questions, Supabase RLS becomes much easier to reason about.
Related FixRLS page
For this specific issue, use the matching FixRLS page: https://fixrls.dev/new-row-violates-row-level-security-policy
Author

Categories
USINGWITH CHECKExample: user-owned projectsSELECT uses USINGINSERT uses WITH CHECKUPDATE often uses bothDELETE uses USINGSummary tableWhy AI-generated fixes can be riskyA better approachA tool I made for thisFinal thoughtOnce you separate those two questions, Supabase RLS becomes much easier to reason about.Related FixRLS pageMore Posts

How I Fixed "new row violates row-level security policy" in Supabase
A practical mental model for fixing Supabase new row violates row-level security policy errors without bypassing RLS.


Using Supabase MCP with Cursor and Claude Code Without Leaking Secrets
A practical checklist for using Supabase MCP with Cursor and Claude Code without leaking powerful secrets.


Is It Safe to Expose Your Supabase Anon Key?
How to reason about visible Supabase anon keys, RLS policies, and what actually protects user data.

Newsletter
Join the community
Subscribe to our newsletter for the latest news and updates