Guide
"new row violates row-level security policy" in Supabase: why data won't save, and how to fix it
Updated
This error means the database is blocking a write it has no rule for. The standard fix (Supabase and PostgreSQL docs) is a policy that lets the signed-in person write their own row, with their user id on it.
In our checks of 44 live AI-built apps, 7 (16%) had a button or form that does nothing or does not save.
What we add from real checks: the save usually works for the owner and fails for a brand-new account on the live address, because the owner tests signed in, often in the preview, with rows that already exist. The person sees a Save button that does nothing; the error only shows in the console or logs. A public example: new row violates row-level security policy for table "players_secure" (supabase/postgres #1708).
In practice that is an INSERT policy, plus a SELECT policy if the app reads the new row back. Do not switch row-level security off.
What the error means
Row-level security (RLS) is a set of rules on each table about who may read or write which rows. Supabase says that once RLS is enabled, "no data is accessible through the API when using a publishable key, until you create policies." PostgreSQL says rows being inserted that do not pass the policy "will result in a policy violation error, and the entire INSERT command will be aborted."
So the database is protecting the table as designed. The fix is a policy that lets the right person write the right row, not switching RLS off.
The causes, and how to fix each one
You can add policies in the Supabase SQL editor. Replace the table and column names with yours.
1. There is no INSERT policy on the table
A table may have a policy for reading but none for adding rows.
How to fix it
- Open the table in the Supabase dashboard and look at its policies.
- Add an insert policy for signed-in users that checks the row belongs to them, adapted from Supabase's profiles example:
create policy "Users can create their own rows" on public.your_table for insert to authenticated with check ( (select auth.uid()) = user_id ); - Save again from the app while signed in.
Source: Supabase: Row Level Security
2. The row does not carry the signed-in user's id
The policy compares a column (such as user_id) with the signed-in user. If the app leaves that column empty, or fills it with another id, the check fails.
How to fix it
- In the code that saves, set user_id to the id of the signed-in user from the current session.
- Make sure the column name in the policy matches the column the app fills.
Source: Supabase: Row Level Security
3. The person is not signed in when the save runs
Supabase says that when a request has no authenticated user, auth.uid() returns null, so a policy like auth.uid() = user_id silently fails for them. It recommends auth.uid() IS NOT NULL AND auth.uid() = user_id. This happens when a form is used before sign-in, or when the session has expired.
How to fix it
- Require sign-in before the screen that saves, or wait until the session exists.
- If the form is meant for visitors who are not signed in (a contact form, for example), write a separate insert policy for the
anonrole that allows only that table and only the fields it needs. See the contact form case.
Source: Supabase: Row Level Security
4. The app asks for the new row back, and there is no SELECT policy
PostgreSQL says that if an insert returns the new row, "any newly inserted or updated rows from the relation must satisfy the relation's SELECT policies", and otherwise an error is thrown. In supabase-js this is an insert followed by .select(), which asks for the new row back.
How to fix it
- Add a select policy so people can read their own rows:
create policy "Users can view their own rows" on public.your_table for select to authenticated using ( (select auth.uid()) = user_id ); - Or, if the app does not need the row back, stop asking for it.
Source: PostgreSQL: CREATE POLICY, Supabase: Row Level Security
5. It is an upsert or an update
An upsert (insert, or update if it exists) needs more than an insert policy. PostgreSQL checks the SELECT policies for an insert with ON CONFLICT DO UPDATE, and the UPDATE policy applies to its update part. Supabase also warns that without a SELECT policy, an UPDATE "will not work as expected."
How to fix it
- Add an update policy for the owner of the row, with both
usingandwith checkcomparing(select auth.uid())with user_id. - Make sure a select policy exists too (cause 4).
- More detail: edits or upserts blocked by row-level security.
Source: PostgreSQL: CREATE POLICY, Supabase: Row Level Security
6. It is a file upload to Supabase Storage
Uploads are rows in the storage.objects table. Supabase says "by default Storage does not allow any uploads to buckets without RLS policies", and that the only policy needed for uploading is INSERT on storage.objects. Overwriting with upsert also needs SELECT and UPDATE.
How to fix it
- Add an insert policy on
storage.objectsfor theauthenticatedrole, limited to your bucket (Supabase's example checksbucket_idand the folder withstorage.foldername(name)). - If the app overwrites files, add select and update policies as well.
Source: Supabase Storage: Access control
How to tell it's fixed
- On the live address (not the preview), sign up as a brand-new account and save a first row. Reload the page: the row is still there.
- Sign up as a second new account. It can save its own rows and cannot see the first account's.
- The browser console and your Supabase logs show no "violates row-level security policy" error for those saves.
The main catch
Switching row-level security off, or putting the secret or service_role key in the app, makes the error disappear and opens the table to anyone. Supabase says to never use a secret key in the browser. The fix is the missing policy, not less security.
Why you cannot see it yourself
You're signed in, your customers aren't. You test as the owner, often from the builder, signed in, with rows that already exist. A brand-new account, or a visitor who is not signed in, is governed by the rules you never tried.
Frequently asked questions
Can I just turn row-level security off?
The error disappears because the table becomes open to anyone with your public key, which is the opposite of what you want. Supabase also says never to use the secret or service_role key in the browser to get around it, because it bypasses RLS. Write the missing policy instead. See users can see other users' data.
The same insert works in the SQL editor but fails from the app. Why?
A public report (supabase/postgres #1708, unanswered) describes a SQL insert working while the same insert fails from the app. The app sends requests as the anon or authenticated role, and Supabase's policies are written for those roles, so test policies as the role your app uses.
I added an insert policy and it still fails. What else?
Check causes 2 to 4: the row's user_id must equal the signed-in user, the person must be signed in, and if the code reads the row back, a select policy is needed.
Can VibeFix fix it for me?
Yes, if you ask. The free check confirms the problem first. Our AI agent can find the cause, fix it in your code and re-check your live app: $79 for one bug, paid up front. If we can't complete your re-check, you get your money back.
Related guides
- Row-level security error saving a profile right after sign-up
- Edits or upserts blocked by row-level security
- Supabase "permission denied for table" (42501)
- Bolt Cloud: contact form insert blocked by row-level security
- Users can see other users' data
- "Database error saving new user" on sign-up
Sources (official documentation)
Fixed it? Prove a real stranger gets in
After the fix, the proof is a brand-new account saving its first row on your live app, and not seeing anyone else's. Your own account cannot show that, because it is not new. Your AI reads your code. VibeFix signs up like a stranger.
Free check. An AI agent signs up to your live app as a brand-new person, with a real inbox of its own, on a phone and on a computer. You get the problem count, what each problem is, and one screenshot. It never changes your app and never emails your users. Test my app, free
VibeFix is independent. Supabase, Lovable, Bolt and Base44 are trademarks of their owners.
Want it fixed?
Our AI agent finds the cause, fixes it in your code and re-checks your live app. $79 for one bug, paid up front. If we can't complete your re-check, you get your money back.
It starts with a free check that confirms the problem on your live app, no account needed.