File Uploads & Storage
Almost every app needs to handle files at some point. A profile photo. A receipt photo. A voice note. A document attached to a message. The user picks something on their phone, and your app stores it somewhere safe so it's still there next time.
That "somewhere safe" is called storage. By default, Appsanic uses Supabase Storage, which lives in the same Supabase project as your database. One place for everything.
Heads-up: file storage is part of the Supabase connector. If you haven't connected Supabase yet, do that first.
The setup prompt
"Let users upload a profile photo. They tap their avatar, pick an image from their camera roll or take a new photo, we resize it to 800×800, upload it, and show it on their profile."
What the agent does:
- Creates a folder for avatars and sets the rules: each user can only read or write their own.
- Adds the native photo picker and camera to the app.
- Resizes the image on the phone before uploading, so it's fast even on slow networks.
- Shows a progress bar during upload.
- Saves the photo's location next to the user's profile so the app can show it everywhere.
You don't see any of that machinery. You see "tap, pick, photo appears."
Public vs private files
This is the only choice you really need to make.
- Public files have a stable web link. Anyone who has the link can open them. Use this for things meant to be shared. Profile photos, photos on a public post, anything users would naturally show off.
- Private files can only be opened by people who are allowed. Use this for receipts, private messages, sensitive documents.
Tell the agent which you want:
"Profile photos are public. Receipts are private. Only the person who uploaded them can view, and access must be checked every time."
For private files, the app authenticates the viewer and checks permission on every request. Appsanic's own account and brand assets follow this model too: their browser links carry no reusable storage token, and Appsanic re-authorizes access before returning bytes. When code is previewed, exported, pushed, or published, referenced assets are bundled as local project files.
Image picking and camera
The agent uses the iPhone and Android native pickers, so the experience feels exactly like what users are used to:
- Pick from photo library.
- Take a new photo.
- Record a video.
- Crop and adjust before upload.
Permission to access photos or the camera is asked at the moment the user taps "Pick photo". Not when the app first opens. Apple and Google reject apps that ask for everything up front, so this is also the polite default.
On-device shrinking
Before uploading, the app usually shrinks the image so it uploads fast and stores cheap. The agent does this automatically. If you want to tune it:
"Resize uploaded images to a maximum of 1600 pixels on the longest side, and compress to good-but-not-perfect quality before upload."
For videos, ask the agent to enable chunked upload and to warn users if they aren't on Wi-Fi.
How big can a file be
Two limits matter:
- Supabase storage has an overall cap that depends on your Supabase plan. The free tier covers a generous amount, paid plans much more.
- A single file: by default the agent caps mobile uploads at 20 MB. Beyond that, uploads start failing on cellular networks. You can raise the cap, but for most apps 20 MB is plenty.
When files should be deleted
When a user deletes a post that had a photo, you also want the photo gone. Otherwise your storage bloats over time with orphan files. Be explicit:
"When a user deletes a post, also delete any photos attached to that post."
The agent ties the deletion of the row and the file together, so they always happen as a pair.
Under the hood (for engineers)
Supabase Storage is S3-compatible object storage with the same Row Level Security model the database uses. Files are namespaced by user ID in their path:
uploads/<user_id>/photo.jpgso the policy can scope cleanly:
create policy "own files read"
on storage.objects for select
using (
bucket_id = 'uploads'
and auth.uid()::text = (storage.foldername(name))[1]
);Public files can use getPublicUrl. Private files should use an authenticated download or an application delivery endpoint that re-checks authorization for every request; do not persist long-lived bearer URLs.
Image picking is expo-image-picker; on-device resize/compress is expo-image-manipulator. Both are included in Expo Go, so you can test uploads without a Dev Client build.
Next
Read Realtime Updates for live feeds and shared data.
