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.objects policies.
  • A sample_uploads table with bucket, 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:

ResourcePolicy behavior
sample_uploadsAuthenticated users can select, insert, and delete rows where user_id = auth.uid()
sample-uploads bucketPrivate bucket, 50 MB file-size limit
storage.objectsAuthenticated 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_uploads rows were inserted.
  • Delete the test account and confirm the user's upload folder is removed.

On this page