Storage
BunBase includes a built-in file storage system with support for local filesystem and S3-compatible providers.
Providers
Section titled “Providers”| Provider | Use case |
|---|---|
local | Development and simple self-hosted deployments |
s3 | Production, CDN-backed, multi-region |
Set STORAGE_PROVIDER=s3 and configure S3 credentials to switch providers. See Configuration.
Buckets
Section titled “Buckets”Files are organized into buckets. Each bucket has its own access policy and optional constraints.
POST /api/v1/admin/bucketsAuthorization: Bearer <admin-secret>Content-Type: application/json
{ "name": "avatars", "is_public": true, "allowed_mime_types": ["image/jpeg", "image/png", "image/webp"], "max_size_bytes": 5242880}The default bucket is created automatically with private access.
Auto-create buckets
Section titled “Auto-create buckets”By default, uploading to a bucket that does not exist returns 404. You can enable automatic bucket creation in Studio › Settings:
- When Auto-create buckets is on, any named bucket that does not exist is created on first upload with authenticated read/write access and no MIME or size restrictions.
- The bucket is created once and reused for subsequent uploads.
Upload a file
Section titled “Upload a file”POST /api/v1/storage/uploadAuthorization: Bearer <access-token>Content-Type: multipart/form-data
file=<binary>bucket=avatarsis_public=trueResponse 201:
{ "id": "01JKX...", "filename": "photo.jpg", "mime_type": "image/jpeg", "size": 204800, "bucket": "avatars", "url": "/api/v1/storage/01JKX...", "is_public": true}Download a file
Section titled “Download a file”GET /api/v1/storage/:idPublic files are accessible without authentication. Private files require a valid Authorization header or a signed URL.
Delete a file
Section titled “Delete a file”DELETE /api/v1/storage/:idAuthorization: Bearer <access-token>Only the file owner or an admin can delete a file.
Signed URLs
Section titled “Signed URLs”Generate a time-limited URL for private file access or direct-upload:
POST /api/v1/storage/signAuthorization: Bearer <access-token>Content-Type: application/json
{ "filename": "photo.jpg", "content_type": "image/jpeg", "bucket": "avatars", "is_public": true, "expires_in": 3600}Returns:
{ "key": "avatars/01JKX....jpg", "url": "<presigned-put-url>", "expires_in": 3600 }The returned url is provider-dependent:
- Local provider: points at
PUT /api/v1/storage/:key?token=...— PUT the file body directly and the server registers metadata in one step. - S3 provider: points directly at your S3 bucket — PUT the file body to S3, then call
POST /api/v1/storage/confirmto register metadata with BunBase.
Confirm S3 upload
Section titled “Confirm S3 upload”After a successful S3 presigned PUT:
POST /api/v1/storage/confirmAuthorization: Bearer <access-token>Content-Type: application/json
{ "key": "avatars/01JKX....jpg", "bucket": "avatars", "filename": "photo.jpg", "mime_type": "image/jpeg", "size": 204800, "is_public": true}Returns a FileRecord (201). BunBase verifies the object exists in storage before registering.
Associate files with records
Section titled “Associate files with records”When uploading, set collection and record_id to link a file to a record:
POST /api/v1/storage/uploadAuthorization: Bearer <access-token>
file=<binary>collection=postsrecord_id=01JKX...Using the TypeScript SDK
Section titled “Using the TypeScript SDK”// Uploadconst file = await client.storage.upload(blob, { bucket: "avatars", isPublic: true,});
// Download URLconst url = client.storage.downloadUrl(file.id, file.filename ?? undefined);
// Deleteawait client.storage.delete(file.id);Streaming uploads
Section titled “Streaming uploads”All uploads — multipart form uploads (POST /storage/upload), presigned PUT via the local provider, and admin uploads — stream file data directly to storage without buffering the entire body in memory.
For large files this means constant memory usage regardless of file size. The streaming path is automatic; no changes are needed in your client code.
If the actual uploaded size exceeds the bucket’s max_size_bytes (even if Content-Length was below the limit), the file is deleted and a 413 is returned.
MIME type validation
Section titled “MIME type validation”If a bucket has allowed_mime_types set, uploads with a non-matching MIME type are rejected with 415 Unsupported Media Type.
Size limits
Section titled “Size limits”If a bucket has max_size_bytes set, uploads exceeding the limit are rejected with 413 Request Entity Too Large.
Image transforms
Section titled “Image transforms”BunBase can resize, crop, and convert images on the fly. Pass query parameters on any image download URL to apply a transform. Only image MIME types are transformed (image/jpeg, image/png, image/webp, image/gif, image/avif). Non-image files are served as-is regardless of params.
Transformed variants are cached on disk after the first request. Subsequent requests for the same transform serve the cached file immediately.
Query parameters
Section titled “Query parameters”| Parameter | Description | Example |
|---|---|---|
w | Output width in pixels (maintains aspect ratio) | ?w=400 |
h | Output height in pixels (maintains aspect ratio) | ?h=300 |
w + h | Resize to fit within the box (maintains aspect ratio, no crop) | ?w=400&h=300 |
fit=cover | Resize and center-crop to exactly w×h | ?w=400&h=300&fit=cover |
fit=contain | Resize to fit within w×h with white letterboxing | ?w=400&h=300&fit=contain |
format | Convert output format: webp, jpeg, or png | ?format=webp |
q | JPEG/WebP quality, 1–100 (default 85) | ?q=75 |
Maximum output dimension on either side is 4096 px (clamped silently).
Examples
Section titled “Examples”# Resize to fit within 800×600, keep original formatGET /api/v1/storage/01JKX...?w=800&h=600
# Thumbnail: crop to exactly 200×200, convert to WebP at quality 80GET /api/v1/storage/01JKX...?w=200&h=200&fit=cover&format=webp&q=80
# Convert to WebP only (no resize)GET /api/v1/storage/01JKX...?format=webp
# Resize width only, keep aspect ratioGET /api/v1/storage/01JKX...?w=400CDN URL
Section titled “CDN URL”You can configure a CDN URL per bucket in Studio › Settings › Storage or via the Admin API. When a bucket has a cdn_url set, download requests for files in that bucket are redirected to the CDN instead of being served through BunBase:
GET /api/v1/storage/:id→ 302 Location: https://cdn.example.com/avatars/01JKX....jpgThis applies to both local and S3 providers. Image transforms (?w=, ?h=, etc.) always go through BunBase even when a CDN is configured, since the transform pipeline needs to read the source bytes.
CDN setup (S3/R2): point your CDN at your S3 bucket, set the bucket’s cdn_url in BunBase, and files are served directly from the CDN edge.
CDN setup (local): point your CDN at {PUBLIC_URL}/api/v1/storage/. Public files can be cached at the CDN edge indefinitely (Cache-Control: public, max-age=31536000, immutable). Private files are served with Cache-Control: private so the CDN will not cache them.
See Configuration for the storage_cdn_url global fallback setting.