Supabase bucket RLS policy for table objects fix
This Storage error almost never means Supabase is broken. It usually means your upload path, your RLS policy, or your use of `upsert` does not match how Storage actually authorizes writes.
Supabase bucket RLS policy for table objects fix#
The exact error looks like this:
new row violates row-level security policy for table "objects"
The root cause is usually one of three things:
- you have no
INSERTpolicy onstorage.objects - you are using
upsert: true, which needs more than justINSERT - you passed the bucket and file path in the wrong places
That is why this error shows up even when the user is already signed in.
The first thing to fix: bucket name vs file path#
This is the broken shape from the real Stack Overflow thread:
// Wrong
await supabase.storage
.from('/public/avatars')
.upload(`${email}.png`, file, { upsert: true })
Supabase's own upload docs show the correct split:
const { data, error } = await supabase.storage
.from('avatars')
.upload('public/avatar1.png', file, {
cacheControl: '3600',
upsert: false,
})
if (error) throw error
from() takes the bucket. upload() takes the path inside that bucket.
The minimal policy for a plain upload#
Supabase documents that the only policy required for uploading objects is an INSERT policy on storage.objects.
A minimal bucket-scoped policy looks like this:
create policy "Allow authenticated uploads to avatars"
on storage.objects
for insert
to authenticated
with check (
bucket_id = 'avatars'
);
If your uploads should go only into a specific folder, add a folder constraint too:
create policy "Allow authenticated uploads to avatars/private"
on storage.objects
for insert
to authenticated
with check (
bucket_id = 'avatars' and
(storage.foldername(name))[1] = 'private'
);
The upsert: true trap#
This is the part many articles miss. Supabase's Storage access-control docs explicitly say that overwriting files with upsert needs SELECT and UPDATE permissions in addition to the upload policy.
So this code:
await supabase.storage
.from('avatars')
.upload(`private/${userId}/avatar.png`, file, {
upsert: true,
})
needs more than just for insert.
If you do not actually need overwrite behavior, make the fix smaller:
await supabase.storage
.from('avatars')
.upload(`private/${userId}/avatar.png`, file, {
upsert: false,
})
That alone resolves a surprising number of production bugs.
If you really do need overwrite behavior#
Keep the insert policy, then add read/update policies that match the same object scope.
create policy "Allow authenticated reads on avatars/private"
on storage.objects
for select
to authenticated
using (
bucket_id = 'avatars' and
(storage.foldername(name))[1] = 'private'
);
create policy "Allow authenticated updates on avatars/private"
on storage.objects
for update
to authenticated
using (
bucket_id = 'avatars' and
(storage.foldername(name))[1] = 'private'
)
with check (
bucket_id = 'avatars' and
(storage.foldername(name))[1] = 'private'
);
A safe client upload example#
const filePath = `private/${userId}/avatar.png`
const { data, error } = await supabase.storage
.from('avatars')
.upload(filePath, file, {
cacheControl: '3600',
upsert: false,
})
if (error) {
throw error
}
This is the right place to start. Only add overwrite behavior once the basic insert path works.
When the service key changes everything#
Supabase's Storage docs also note that service keys bypass Storage RLS entirely. That can be useful for trusted server-side jobs, but it is not a fix for a browser upload bug. If a client upload needs the service key to work, the policy is still wrong.
The better debugging order is:
- fix the bucket/path split
- test with
upsert: false - add the exact
INSERTpolicy - only then add
SELECTandUPDATEif overwrite is required
For the surrounding security pieces:
- Supabase Storage: Guide to File Uploads and Management
- File Storage and Media Handling with Next.js and Supabase
- Why Your Supabase RLS Policies Are Silently Failing (And How to Debug Them)
- Next.js + Supabase Security: RLS, Secrets, and the Mistakes That Leak Data
References#
Frequently Asked Questions
One email a month — no fluff
RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.
Continue Reading
Debugging Supabase RLS Issues: A Step-by-Step Guide
Master RLS debugging techniques. Learn how to identify, diagnose, and fix Row Level Security policy issues that block data access in production.
Why Your Supabase RLS Policies Are Silently Failing (And How to Debug Them)
RLS failures don't throw errors — they return empty results. Here is exactly how to find and fix the most common Row Level Security bugs in Supabase before they reach production.
Supabase Service Role Key Guide 2026: Secure RLS Bypass
Learn how to securely use the Supabase service role key in Next.js Edge Functions and Server Actions to bypass RLS and manage users.
Browse by Topic
Find stories that matter to you.
