Storage
Use the sample upload flow and Supabase Storage policies.
The Upload sample is an Example feature that shows private Supabase Storage
uploads from Expo: pick files, stream upload progress, write a queryable
sample_uploads row, and list recent uploads for the signed-in user.
What is included
The step7_examples migration creates everything the sample needs:
- A private Storage bucket named
sample-uploads. - A 50 MB bucket file-size limit.
- Folder-scoped
storage.objectspolicies. - A
sample_uploadstable withbucket,path, file metadata, and RLS.
No dashboard bucket setup is required if you run:
supabase db push --project-ref <ref>Upload flow
app/upload-sample.tsx renders the screen. lib/sample-uploads.ts owns the data
and upload mechanics.
Pick files
The screen calls DocumentPicker.getDocumentAsync({ multiple: true, type: "*/*" }).
It supports multi-file queues and shows per-file progress.
Build a private object path
Paths are generated as:
<user-id>/<timestamp>-<random>-<sanitized-filename>The first path segment is the user's UUID so Storage policies can compare it to
auth.uid().
Upload with progress
The app reads bytes with expo-file-system, then uploads directly to the
Supabase Storage REST endpoint with XMLHttpRequest so progress events can
update the queue UI.
Insert metadata
After the object upload succeeds, the app inserts a sample_uploads row with
the bucket, path, original filename, content type, byte size, and user ID.
This differs from the Swift port: the Expo original writes both the Storage
object and a sample_uploads table row, so you can query recent uploads
without listing the bucket.
Policies
The migration scopes rows and objects to the signed-in user:
| Resource | Policy behavior |
|---|---|
sample_uploads | Authenticated users can select, insert, and delete rows where user_id = auth.uid() |
sample-uploads bucket | Private bucket, 50 MB file-size limit |
storage.objects | Authenticated users can read, insert, update, and delete objects where bucket_id = "sample-uploads" and (storage.foldername(name))[1] = auth.uid()::text |
Keep the <user-id>/... folder convention if you swap in your own bucket, or
rewrite the policies to match your path layout.
Display and cleanup
Recent uploads render from the sample_uploads table through useSampleUploads.
The sample shows metadata rows; add signed URL generation if your product needs
previews or downloads.
Account deletion is handled by the delete-account Edge Function. It lists
files under sample-uploads/<user-id>/, removes those objects, then deletes the
auth user. sample_uploads rows cascade through the auth user foreign key.
Avatar bucket note
lib/profile.ts includes avatar upload helpers that compress images with
expo-image-manipulator, upload to an avatars bucket, and store the avatar
path on profiles.avatar_url. The shipped migrations do not create that bucket.
If you keep avatar uploads, create an avatars bucket and owner-scoped policies
before exposing the feature in production.
Customize storage
- Change the bucket constant in
lib/sample-uploads.ts. - Add accepted file types in the DocumentPicker options.
- Keep the metadata insert if you need fast lists, audit trails, or cleanup UI.
- Add signed URL helpers for previews, downloads, and image rendering.
- Mirror the folder-scoped policies for any new private bucket.
Verify uploads
- Sign in, open Upload sample, and choose one or more files.
- Confirm the queue reaches "Uploaded" and recent uploads refresh.
- In Supabase Storage, confirm objects live under
sample-uploads/<user-id>/. - In the SQL editor, confirm
sample_uploadsrows were inserted. - Delete the test account and confirm the user's upload folder is removed.
