Guide Supabase Storage
Is my Supabase storage bucket public? How to check and fix
Short answer
Supabase buckets are private by default. A public bucket (created that way or switched later, sometimes by an AI builder's migration) lets anyone with a file's URL open the file. Check in Storage or run select id, public from storage.buckets;. Public is fine for logos and avatars. For anything personal, make the bucket private, add policies on storage.objects and serve files with signed URLs, updating your app in the same change because the public URLs stop working. Copies cached by Supabase's CDN or a browser can keep loading for a while, so treat anything that was public as already seen.
What does "public bucket" actually mean?
There are two kinds of bucket:
- Private (the default). "When a bucket is set to Private all operations are subject to access control via RLS policies. This also applies when downloading assets."
- Public. It "effectively bypasses access controls for both retrieving and serving files within the bucket. This means that anyone who possesses the asset URL can readily access the file."
For public buckets, "Access control is still enforced for other types of operations including uploading, deleting, moving, and copying." (Supabase: Storage Buckets)
Supabase's examples of public content: user profile pictures, user public media and blog post content. Of private: users' sensitive documents.
Public files are served from a URL like this (Supabase: Serving assets):
https://[project_id].supabase.co/storage/v1/object/public/[bucket]/[asset-name]
If your app's links contain /object/public/, that bucket is public.
How do I check which buckets are public?
In the dashboard: open Storage, then the Buckets tab (Storage opens on Files). Public buckets show a PUBLIC badge next to their name.
In Lovable Cloud: go to More → Cloud → Storage. Each bucket card shows Private or Public. Lovable notes that "Public storage buckets are blocked by default on all plans," so a public bucket there usually means a workspace owner or admin turned that block off. (Lovable: Storage)
In the SQL editor (Supabase):
-- 1. Which buckets are public?
select id, name, public
from storage.buckets
order by public desc, name;
-- 2. How many files sit in each public bucket?
select b.id as bucket, count(o.id) as files
from storage.buckets b
left join storage.objects o on o.bucket_id = b.id
where b.public
group by b.id
order by b.id;
For each public bucket, ask: does anything here belong to one user (invoices, IDs, exports, private uploads)? If yes, it shouldn't be public.
Can people list the files in my bucket?
Listing is different from opening a file by URL. A public bucket's URLs work without any select policy. Supabase's Security Advisor warns about one mistake (lint 0025_public_bucket_allows_listing, "Public Bucket Allows Listing", level WARN): a public bucket that also has a broad select (or all) policy on storage.objects for the public, anon or authenticated roles, such as bucket_id = 'avatars' or true. That combination "allows API clients to list objects in the bucket," so anyone can find files whose URLs they didn't already know. (Supabase: Database Advisors, lint 0025)
See your storage policies:
select policyname, roles, cmd, qual, with_check
from pg_policies
where schemaname = 'storage' and tablename = 'objects'
order by policyname;
If a public bucket has a broad select policy you don't need, drop it (use its real name from the query above). Supabase says public object URLs "will continue to work after removing the SELECT policy."
drop policy if exists "Public bucket listing" on storage.objects;
If you need a select policy that allows downloads but not listing, Supabase offers the helpers storage.allow_any_operation() and storage.allow_only_operation(). (Supabase: Storage Helper Functions)
How do I make private files private?
Do these steps together, in one change.
1. Make the bucket private (Supabase dashboard or SQL; in Lovable Cloud, ask Lovable in the project chat):
update storage.buckets
set public = false
where id = 'invoices';
2. Add policies on storage.objects. Supabase: "By default Storage does not allow any uploads to buckets without RLS policies." (Supabase: Storage Access Control) These two follow Supabase's examples: each user uploads into a folder named after their user ID and reads only the files they uploaded:
create policy "invoices_insert_own_folder"
on storage.objects for insert to authenticated
with check (
bucket_id = 'invoices'
and (storage.foldername(name))[1] = (select auth.jwt()->>'sub')
);
create policy "invoices_select_own"
on storage.objects for select to authenticated
using (
bucket_id = 'invoices'
and (select auth.jwt()->>'sub') = owner_id
);
Swap invoices for your bucket. Uploads must use paths like <user-id>/file.pdf. Supabase notes that overwriting files with upsert also needs select and update permissions.
3. Serve files with signed URLs. Your app either downloads private files with the signed-in user's session or creates a time-limited link (Supabase: Serving assets):
const { data, error } = await supabase.storage
.from('invoices')
.createSignedUrl('user-id/invoice-123.pdf', 3600) // valid for 1 hour
In Lovable Cloud, Copy URL on a private file gives "a temporary signed URL that expires after one hour."
Will making a bucket private break my app?
It can. Supabase says files in a non-public bucket "are not accessible via a public URL like the public buckets." So every /object/public/... link your app saved or shows for that bucket stops working once you switch.
Old links can still load for a while, though. Supabase's CDN docs don't say the switch clears copies the CDN already cached, and browsers keep files until their cache time runs out (Supabase's default is typically one hour). On Pro plans and above you can purge the bucket's CDN cache from a server with the secret key. Never do it from the browser. To cut off access to a specific file, Supabase's advice is to delete it. (Supabase: Smart CDN, Purge CDN Cache)
Before you switch:
- Search your code for
getPublicUrl(and/object/public/for that bucket. - Check for saved public URLs in your database (for example an
avatar_urlorfile_urlcolumn). - Check who owns the files already in the bucket. The read policy above only shows a file to the user whose ID is in its
owner_id, so files uploaded with the service key, by an Edge Function or while signed out won't be readable. Serve those from server code or move them into the right user's folder. - Change those spots to use signed URLs or authenticated downloads, then switch the bucket.
About signed URLs: they "remain valid until their expiry time regardless of any Auth key changes," and revoking them early means contacting Supabase support, so keep expiry times short. With Supabase's Smart CDN, a cached response for a signed URL can keep being served after the token expires, until the CDN cache time runs out.
What if public is the right choice?
Keep it public for files meant for everyone, with no broad select policy unless you want people to list the files. Supabase says public buckets "are more performant than private buckets since they are cached differently."
One number: Sentris found a public storage bucket in 38.7% of Lovable repos, compared with 10.5% of repos with no AI-tool marker. (Source: Sentris, Aug 5, 2026, a scan of 2,144 public Supabase repositories. Sentris sells a scanner, so treat this as vendor research. Sentris may become a referral partner.) A public bucket isn't a leak by itself. It matters when the files in it are private.
FAQ
Is my bucket private if I never changed anything?
Probably, since buckets are private by default, but an AI builder's migration can create a public one. Run the check anyway. In Lovable Cloud, the workspace setting Block public storage buckets is also on by default and forces new buckets to private unless a workspace owner or admin turns it off.
Can the service key read private buckets?
Yes. "Service keys entirely bypass RLS policies," so the service or secret key must never be in your frontend.
Want a second pair of eyes?
Free check: A passive check of your public homepage, its scripts and its security headers, and nothing else: we look for exposed secret keys, missing headers and public storage links found in the code (the bucket itself is never opened or listed). No database queries, no logins, no changes. One page, within 24 hours. Owners only. Request a free check
Rather have it fixed? Security Fix ($149) covers public storage buckets holding private files, along with RLS and exposed keys. Within 48 hours of working access or a full refund (Refund Policy). See Security Fix
Sources
- Supabase: Storage Buckets (private by default, public access model, upload/delete still controlled, performance, examples).
- Supabase: Serving assets from Storage (public URL format, private assets not reachable via a public URL,
createSignedUrl, signed URL validity and revocation) - Supabase: Storage Access Control (no uploads without policies, policy examples, upsert, service key bypass)
- Supabase: Database Advisors, lint 0025 (public bucket allows listing, broad
select/allpolicies, drop policy, URLs keep working,update storage.buckets). - Supabase: Storage Helper Functions (
allow_any_operation,allow_only_operation) - Supabase: Smart CDN and Purge CDN Cache (cache after a switch, signed URLs and CDN caching, purge on Pro and above, delete to cut off access)
- Lovable: Storage (More → Cloud → Storage, Private default, public blocked by default, signed URL Copy URL expires after one hour)
- Sentris, Aug 5, 2026 (One number).