Short answer
The table has RLS on and no INSERT policy that this row passes. Add one whose WITH CHECK matches the row, usually user_id = auth.uid(), and make sure the insert sets user_id for a signed-in user. If you call .select() after the insert, or use upsert, the table also needs a SELECT policy the new row passes. Don’t fix it by turning RLS off.
The usual causes, and the fix
01
There is no INSERT policy
With RLS on, every operation needs its own policy. A SELECT policy lets users read rows but doesn’t let them add any. Add an INSERT policy with a WITH CHECK clause that ties the new row to the user.
create policy "Users add their own rows" on profiles for insert to authenticated with check ((select auth.uid()) = user_id);02
The new row fails the WITH CHECK
The policy exists, but this row doesn’t pass it. Usually the insert leaves user_id empty, sends a different ID, or runs before sign-in finishes, so the request runs as a signed-out user. Set user_id from the signed-in session, or give the column a default so the database fills it in.
alter table profiles alter column user_id set default auth.uid();03
.select() after the insert, or upsert
“If a newly inserted or updated row does not satisfy the relation's SELECT policies, an error will be thrown” PostgreSQL docs, CREATE POLICY
Calling .select() after .insert() asks Postgres to return the new row, so the row must also pass a SELECT policy. Upsert needs a SELECT policy and, to overwrite existing rows, an UPDATE policy too.
04
Uploading a file to Storage
Supabase Storage keeps files in the storage.objects table, which has RLS on. An upload needs an INSERT policy on storage.objects for that bucket; overwriting a file with upsert also needs SELECT and UPDATE policies.
create policy "Users upload to their own folder" on storage.objects for insert to authenticated with check ( bucket_id = 'avatars' and (storage.foldername(name))[1] = (select auth.uid())::text );
Checked against the vendor’s docs on : Supabase docs: Row Level Security; Supabase docs: Storage access control; PostgreSQL docs: CREATE POLICY.
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
- from $450AI-Built App RescueA Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way.See scope and start
Related: Free Supabase RLS policy checker · Supabase RLS fixes · “Permission denied” on new tables