UnbreakMyApp / Fix guides

Images not loading from Supabase Storage (broken image or 400/403)

Uploaded photos show as broken images, or uploads fail. How to fix private buckets, storage policies and wrong URLs in Lovable and Bolt apps.

Building a new app instead? Try Bolt (20% off with our link) (affiliate link).

Why it happens

Supabase Storage buckets are private by default. A URL from getPublicUrl() only works if the bucket is marked Public; for private buckets you need a signed URL from createSignedUrl().

Uploads (and reads from private buckets) are controlled by RLS policies on storage.objects. Without an INSERT policy, uploads fail with "new row violates row-level security policy".

Other common causes: saving the full URL from preview and later changing projects, file paths with spaces or different capitalization, or images uploaded to a different bucket than the code reads from.

How to fix it, step by step

  1. Copy a broken image URL from the browser (right-click → Copy image address) and open it in a new tab to see the error.
  2. In Supabase → Storage, check whether the bucket is Public. For things like avatars and product photos, Public is fine. For private documents, keep it private and use signed URLs.
  3. In Storage → Policies, add policies for storage.objects scoped to your bucket: INSERT for authenticated users (ideally limited to their own folder, e.g. a path that starts with their user id), and SELECT if the bucket is private.
  4. Store the file path in your database rather than the full URL, and build the URL when displaying it.
  5. Check the bucket name and path in your code match exactly.
  6. Upload a new test image and confirm it displays on the live site.

Paste-ready prompt for Lovable

Images from Supabase Storage are not loading (or uploads fail). Please:
1. Tell me which bucket the app uses and whether it is public or private.
2. If images should be public (avatars, product photos), make the code use getPublicUrl and tell me to mark the bucket public. If private, use createSignedUrl with a reasonable expiry.
3. Write storage.objects policies: authenticated users can upload to their own folder ((storage.foldername(name))[1] = auth.uid()::text), and read rules that fit the bucket.
4. Store file paths, not full URLs, in the database.
Explain the policies in plain English before applying.

FAQ

Is a public bucket safe?

Anyone with the link can view the files, so only use it for content meant to be public. Uploads still need a policy.

Why do my signed URLs stop working?

Signed URLs expire. Generate them when the page loads instead of saving them.

Still broken?

Get a free diagnosis by email within 24 hours. Fixed or refunded.

Get a free diagnosis

Or get the weekly fix digest:

📬 This Week in Broken Apps: the most common Lovable/Supabase breaks and how to fix them. Free, weekly, unsubscribe anytime.

Free: the Vibe-Code Fix Checklist.

Sources