Cenk Kurtoğlu©26

Supabase security · 5 minute read

Supabase Storage upload fails with a 403 row-level security error

Your upload throws new row violates row-level security policy (HTTP 403) — but the exact same upload works with the service_role key. That one detail is the whole diagnosis: files in Supabase Storage are rows in the storage.objects table, RLS applies to that table, and service_role bypasses RLS. So the key is not the fix — your storage.objects policy simply does not match the request you are actually making.

Why service_role "works" and your app key does not

service_role ignores every policy, so a successful upload with it proves nothing about your rules. Your browser or server upload runs as anon or authenticated, and those go through RLS on storage.objects. If no policy grants an INSERT to that role for that bucket, you get the 403.

The four things that actually cause it

A working authenticated-upload policy

For "each signed-in user can upload into their own folder in the avatarsbucket":

create policy "authenticated users upload to own folder"
on storage.objects for insert
to authenticated
with check (
  bucket_id = 'avatars'
  and (storage.foldername(name))[1] = auth.uid()::text
);

Upload to the path avatars/<user-id>/file.png and it passes. Upload to any other prefix and RLS correctly rejects it.

Confirm which role your upload runs as

Run this inside the same context that is failing. If it returns anon while your policy expects authenticated, the request is unauthenticated — that is the real problem, not the policy:

select auth.role() as role, auth.uid() as uid;

The public RLS leak demo runs a read-only harness that queries as anon and as an authenticated user, so you can see exactly which role a request runs as before you touch a policy.

Check every policy and role in one pass

Supabase RLS Audit Kit · $29 one-time

Seven commented SQL audits and 60 checks show every table's RLS status, the role each policy actually targets (including storage.objects), grants and bypass paths, plus a role-simulation harness that proves isolation. Report and fix-SQL templates included.

Get the kit - $29 ↗Review the files, scope and license →

Related reading: why Supabase RLS returns nothing or 42501 on the server and 7 ways your Supabase app still leaks data with RLS enabled. For a full launch pass, a fixed-price Supabase RLS audit from $99 returns prioritized findings with reproducible evidence.

Template bundle

Review all 20 frontend starters in one bundle

20 editable frontend starters with live demos are $79 — one payment, no subscription.

View the Bundle — $79