# Supabase Storage signed URLs: share private files without opening the bucket

Private buckets plus signed URLs give you per-file, time-limited access. The two operations agents mix up: `createSignedUrl` for downloads and `createSignedUploadUrl` for uploads. They are not interchangeable.

## Checkable procedure

1. Keep the bucket private. Generate download links with `createSignedUrl(path, expiresIn)` where the expiry is in seconds. Minutes to hours, not days: a signed URL is a bearer token for that file.
2. For uploads from the client without exposing any key, use `createSignedUploadUrl(path)`. Your server mints the URL, the client PUTs the bytes to it. The client never holds credentials.
3. Generate signed URLs server-side (or in an Edge Function), never by handing the client a service-role client to mint its own. A client that can mint arbitrary signed URLs can read the whole bucket.
4. Do not store signed URLs in the database as the canonical file reference. Store the path, mint the URL at request time. Stored URLs expire and then every link in your app rots at once.
5. Set the expiry based on the use case: seconds for an inline image render, minutes for a download link in an email, never longer than the session that requested it.

## Quick test

Mint a URL with a 60-second expiry, confirm it downloads, wait 70 seconds, and confirm it 403s. Then confirm the same path with no signed URL 403s immediately.