FixedLaunchBack

fix guide

New row violates row-level security policy in Supabase

· · updated

All fix guides

You save a form or upload a file, and the app fails. The error reads: new row violates row-level security policy for table, followed by the table name. The row never reaches your database.

Supabase blocks the write because no policy allows it. Row Level Security (RLS) makes the database check every write against your rules. The fix is almost always a policy. Check these five in order. For other RLS errors, such as infinite recursion or an empty list, see /guides/supabase-rls-errors.

1. RLS is on and the table has no insert policy

Once RLS is on for a table, the public key can't reach its rows until you add policies. Supabase's docs say no data is accessible through the API with a publishable key until policies exist. Each action needs its own policy: select, insert, update and delete.

Open the Policies page for the table in the Supabase dashboard. If there is no insert policy, add one. This example lets a signed-in user add rows that carry their own id. Replace todos with your table name:

create policy "Users can add their own rows" on todos for insert to authenticated with check ((select auth.uid()) = user_id);

Do not fix this by turning RLS off. The public key sits in your site's code, so every visitor can use it. Turning RLS off can open the whole table to them.

Do not put the service_role key in the browser to get around it. That key bypasses RLS, and Supabase says it should never be shared publicly.

signs Every save to this table fails, for signed-in and signed-out users alike.

2. The row fails the policy's check

An insert policy uses a with check rule. The database tests each new row against it before it saves the row. If the test fails, the insert is blocked.

The example above needs the row's user_id to match the signed-in user's id. So the insert must send user_id. If the form leaves it out, or sends a different id, the check fails.

Ask your builder to show the save code and confirm it sends user_id with the signed-in user's id. Then check that the table column has that exact name. If the column is called owner_id, the policy has to use owner_id too.

signs The save code never sets user_id, or it sends a column with another name.

3. The user isn't signed in

Most policies compare the row with auth.uid(), the id of the person making the request. A visitor who isn't signed in has no id, so auth.uid() comes back empty. The comparison fails and the write is blocked.

Supabase's docs recommend checking for a signed-in user inside the policy, so the rule says what it means:

with check (auth.uid() is not null and auth.uid() = user_id)

A policy written to authenticated only applies to signed-in users. Visitors who aren't signed in use the anon role, and this policy doesn't cover that role.

signs Saving works right after sign-in and fails when you open the site in a private window.

4. Storage uploads need their own policies

Uploaded files are stored in a table called storage.objects. By default, Storage allows no uploads to a bucket without RLS policies. The one policy an upload needs is an INSERT policy on storage.objects. This example lets signed-in users upload to one bucket. Replace my_bucket_id with your bucket name:

create policy "Signed-in users can upload" on storage.objects for insert to authenticated with check (bucket_id = 'my_bucket_id');

An upsert replaces a file that already exists. For that, add a SELECT policy and an UPDATE policy on storage.objects too. The docs list both as needed for overwrites.

signs The upload fails with the row-level security error, and no new file appears in the bucket.

5. The role has no grant on the table

A policy only runs after the role has permission on the table. Supabase's docs say a missing grant fails before any policy is checked. The message then says "permission denied for table" instead of the row-level security wording. Some projects don't grant these rights automatically.

Grant the actions your app uses to the authenticated role. This line covers all four:

grant select, insert, update, delete on table todos to authenticated;

Grant only what the app needs. Then check the policies from steps 1 to 4 again.

signs The message says "permission denied for table", not "new row violates row-level security policy", and the policies look right.

Test it like a new user

  1. Open the live site in a private window and sign in.
  2. Save one new row and check it appears in the Supabase table with your user_id.
  3. Upload one file to the bucket and check it appears in Storage.
  4. Sign in as a second user and check they can't change the first user's rows.
  5. Sign out and check that a save is refused.

Common questions

How do I fix new row violates row-level security policy for my table?

Open the Policies page for the table in the Supabase dashboard and add an insert policy. It should let a signed-in user add rows that carry their own id. Do not turn RLS off, because anyone with your public key could then read and change the table.

Why does my storage upload fail with a row-level security error?

Uploaded files are stored in storage.objects, which needs its own INSERT policy. By default Storage allows no uploads to a bucket without RLS policies. Add a policy on storage.objects for insert to authenticated users, limited to your bucket name. An upsert also needs SELECT and UPDATE policies.

Why does my save fail with permission denied for table even though the policies look right?

The role has no table grant. A policy only runs after the role has permission on the table, and a missing grant fails before any policy is checked. That error says permission denied for table instead of new row violates row-level security policy. Grant only the actions your app uses to the authenticated role, then check the policies again.

Still stuck?

Not sure this is the cause? Paste your app's link into the free scan. It lists what a visitor's browser can see, like leaked keys and exposed files, worst first. The score is free; every fix as a prompt for your builder is $9, one-time.

Want a person to look inside? See the diagnosis

More fix guides

See all fix guides