Route: /p/:id/storage. Source: apps/studio/src/features/storage, backed by apps/server/controllers/admin-storage.controller.ts.
Buckets
| Action | Endpoint |
|---|---|
| List | GET /admin/storage/buckets |
| Create | POST /admin/storage/buckets |
| Delete | DELETE /admin/storage/buckets/:slug?force=<bool> |
| Deletion job status | GET /admin/storage/bucket-deletions/:id |
Deleting a non-empty bucket needs force. That does not run inline: it creates a BucketDeletionJob that the deletion scheduler drains with a lease, tracking totalObjects / deletedObjects / attempts. The screen polls the job id for progress, and the job survives a restart — see the data model.
Objects
| Action | Endpoint |
|---|---|
| List (paged) | GET /admin/storage/buckets/:bucket/files?… → { rows, total } |
| Upload | POST /admin/storage/files (multipart) |
| Signed URL | GET /admin/storage/files/:id/url |
| Delete | DELETE /admin/storage/files/:id |
Upload limits come from MAX_UPLOAD_MB (default 25) and the storage.limits settings group; per-bucket limits are clamped to the global ceiling.
CORS
GET /admin/storage/buckets/:bucket/cors
PUT /admin/storage/buckets/:bucket/cors
DELETE /admin/storage/buckets/:bucket/corsA BucketCors row holds allowedOrigins, allowedMethods, allowedHeaders and maxAgeSec (default 3600). It is enforced by middlewares/bucketCors.middleware.ts on the public storage routes — independent of the project-wide server.cors group, which is edited in one place only: Project Settings → Security.
Scoped tokens
GET /admin/storage/buckets/:bucket/tokens
POST /admin/storage/buckets/:bucket/tokens
DELETE /admin/storage/buckets/:bucket/tokens/:idA BucketToken carries permissions[] (read, write, delete) and an optional expiresAt. It authenticates the public storage API (bucketTokenGuard) without a user session:
POST /api/storage/:slug/upload requires write
GET /api/storage/:slug/files/:id
DELETE /api/storage/:slug/files/:id requires deleteBucket tokens are bearer credentials scoped to one bucket. Through the Console proxy, listing or creating them requires secrets.manage. Prefer short expiresAt values for anything handed to a client.
Per-bucket webhooks
GET /admin/storage/buckets/:bucket/webhooks
POST /admin/storage/buckets/:bucket/webhooks
DELETE /admin/storage/buckets/:bucket/webhooks/:idBucketWebhook rows are url + events[], separate from project-level event webhooks.
Public vs private
Only the public bucket (S3_PUBLIC_BUCKET) is served statically. Private objects are reachable only through the authenticated file endpoints — there is no guessable static URL for them. Backend, drivers (local / s3) and configuration: Storage.
Protocol and image endpoints
The project storage service also exposes the S3-compatible protocol when S3 support is configured, and resumable uploads through TUS 1.0.0. These are data-plane interfaces, not additional Studio bucket records; quotas, visibility and bucket policies still come from the same project configuration.
Image transforms are served by /storage/v1/render/image. The SDK can request dimensions,
resize mode, quality and output format through download, getPublicUrl, or
createSignedUrl; SVG inputs are not transformable and transformed signed URLs expire.