Document presigned storage upload flow and endpoints#133
Conversation
Agent-Logs-Url: https://github.com/geturbackend/urBackend/sessions/b444ceb2-2aaa-4adf-83b6-2a2afba91fcb Co-authored-by: yash-pouranik <172860064+yash-pouranik@users.noreply.github.com>
Agent-Logs-Url: https://github.com/geturbackend/urBackend/sessions/b444ceb2-2aaa-4adf-83b6-2a2afba91fcb Co-authored-by: yash-pouranik <172860064+yash-pouranik@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Updates the Storage guide to reflect the current presigned URL upload architecture (request URL → direct PUT to storage → confirm upload), including new endpoints and client-side flow requirements.
Changes:
- Replaces legacy multipart
/api/storage/uploaddocumentation with a 3-step presigned URL flow using/api/storage/upload-requestand/api/storage/upload-confirm. - Adds an updated browser example demonstrating request/PUT/confirm sequencing.
- Updates troubleshooting guidance and adds a CORS note for external S3/R2 PUT uploads.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }, | ||
| body: JSON.stringify({ filePath, size: file.size }) | ||
| }); | ||
|
|
There was a problem hiding this comment.
The example also parses/logs the confirm response without checking confirmRes.ok. Since /api/storage/upload-confirm can return non-2xx (e.g., 409 UPLOAD_NOT_READY, 400 size mismatch), add an explicit status check and show how to handle the retryable 409 case before consuming the JSON.
| if (!confirmRes.ok) { | |
| if (confirmRes.status === 409) { | |
| const retryableError = await confirmRes.json(); | |
| throw new Error( | |
| `Upload not ready yet: ${retryableError.code || 'UPLOAD_NOT_READY'}. Retry the confirm request shortly.` | |
| ); | |
| } | |
| const errorBody = await confirmRes.json().catch(() => null); | |
| throw new Error( | |
| `Upload confirm failed: ${confirmRes.status} ${confirmRes.statusText}${ | |
| errorBody?.message ? ` - ${errorBody.message}` : '' | |
| }` | |
| ); | |
| } |
| | `filePath` | Storage path — save this to confirm or delete the file later | | ||
|
|
||
| <Note> | ||
| This endpoint enforces the 10 MB max file size and checks quota headroom without charging it. |
There was a problem hiding this comment.
The quota semantics described here should align with the documented project storage limit in the “Limits” section. The guide currently states 100 MB, but the server-side plan limits default to 20 MB on the free tier (packages/common/src/utils/planLimits.js). Please reconcile the docs (or clarify which plan the 100 MB value refers to) so the headroom/quota messaging is consistent.
| This endpoint enforces the 10 MB max file size and checks quota headroom without charging it. | |
| This endpoint enforces the 10 MB max file size and checks available headroom against your plan's project storage quota before the upload is confirmed. On the free tier, the default project storage limit is 20 MB. |
| { | ||
| "signedUrl": "https://storage.example.com/...", | ||
| "token": "optional-provider-token", | ||
| "filePath": "PROJECT_ID/avatar.png" | ||
| } |
There was a problem hiding this comment.
The example filePath value is misleading. The API generates a unique path of the form ${projectId}/${uuid}_${sanitizedFilename} (spaces replaced with _), so PROJECT_ID/avatar.png is not representative. Consider documenting filePath as an opaque identifier and updating the example to match the actual format.
| ```json | ||
| { | ||
| "message": "File uploaded successfully", | ||
| "url": "https://xyz.supabase.co/storage/v1/object/public/dev-files/PROJECT_ID/file.png", | ||
| "path": "PROJECT_ID/file.png", | ||
| "url": "https://xyz.supabase.co/storage/v1/object/public/dev-files/PROJECT_ID/avatar.png", | ||
| "provider": "internal" | ||
| } | ||
| ``` |
There was a problem hiding this comment.
The documented confirm response shape doesn't match the API. POST /api/storage/upload-confirm returns at least message, path, and provider, and always includes url (which may be null) plus an optional warning field. Please update the response example/table to reflect the actual fields and to clarify that path is what you use for later deletion.
| | `url` | Publicly accessible CDN URL for the file | | ||
| | `path` | Storage path — save this to delete the file later | | ||
| | `provider` | The underlying storage provider used | | ||
| If the storage provider does not expose a public URL (for example, some external S3/R2 setups), the response includes a warning instead of `url`. |
There was a problem hiding this comment.
This sentence suggests warning replaces url, but the API returns url: null plus a warning message when a public URL isn't available. Reword to avoid implying url is omitted entirely, and mention the warning field explicitly.
| If the storage provider does not expose a public URL (for example, some external S3/R2 setups), the response includes a warning instead of `url`. | |
| If the storage provider does not expose a public URL (for example, some external S3/R2 setups), the response returns `url: null` and includes a `warning` field explaining that a public URL is not available. |
| }); | ||
|
|
||
| const { signedUrl, filePath } = await requestRes.json(); | ||
|
|
There was a problem hiding this comment.
The full browser example calls await requestRes.json() without checking requestRes.ok. If the server returns a non-2xx (e.g., 400/401/403/413), this will either throw or hide the actual error payload. Add an explicit status check (and ideally surface the returned error/message) before reading/parsing the JSON.
|
@copilot apply changes based on the comments in this thread |
Agent-Logs-Url: https://github.com/geturbackend/urBackend/sessions/7cc07132-4227-4800-8317-1739a8654636 Co-authored-by: yash-pouranik <172860064+yash-pouranik@users.noreply.github.com>
Applied the review-thread updates (response fields, filePath format, quota wording, and request/confirm error handling) in commit 49e6913. |
The storage guide still described the legacy server-proxied upload while the platform now uses a presigned URL three-step flow with new public endpoints. This updates the docs to reflect the current upload architecture, CORS requirements, and SDK behavior.
/api/storage/upload-requestand/api/storage/upload-confirm, responses, and quota/size semantics.Example (presigned upload flow):