File Storage
Butterbase provides file storage with presigned URLs. Files are organized per-app and per-user. Your frontend uploads and downloads files directly — file data never flows through your backend code.
How it works
Section titled “How it works”- Request an upload URL — Your app asks Butterbase for a presigned upload URL, providing the filename, content type, and size.
- Upload directly — Your frontend uses the presigned URL to upload the file directly to storage.
- Reference the file — Store the returned
objectIdin your database (e.g., as animage_urlcolumn). - Download when needed — Request a presigned download URL using the object ID.
Object ID, object key, and URLs
Section titled “Object ID, object key, and URLs”| Value | What it is | What to do with it |
|---|---|---|
objectId | A stable UUID for this file | Persist this in your database. Use it for downloads and deletes. |
objectKey | The path inside the bucket | Not a URL. Metadata only; do not store for display. |
uploadUrl / downloadUrl | Temporary presigned HTTPS URLs | Use only for immediate operations. They expire. |
Common mistakes
Section titled “Common mistakes”- Saving
objectKeyas a URL — It’s a path, not a URL. The UI will show broken images. - Using
objectKeyasimg src— UseobjectIdwith the download endpoint to get adownloadUrl. - Storing only a presigned URL — Presigned URLs expire. Store
objectIdas the source of truth.
Uploading a file
Section titled “Uploading a file”Step 1: Request the upload URL.
POST /storage/{app_id}/uploadAuthorization: Bearer {token}
{ "filename": "profile.jpg", "contentType": "image/jpeg", "sizeBytes": 102400}Response:
{ "uploadUrl": "https://storage.example.com/...", "objectKey": "app_id/user_id/uuid_profile.jpg", "objectId": "uuid", "expiresIn": 300}Step 2: Upload the file using the presigned URL.
await fetch(uploadUrl, { method: 'PUT', headers: { 'Content-Type': 'image/jpeg' }, body: fileBlob});Step 3: Save the objectId in your database.
Downloading a file
Section titled “Downloading a file”GET /storage/{app_id}/download/{object_id}Authorization: Bearer {token}Response:
{ "downloadUrl": "https://storage.example.com/...", "filename": "profile.jpg", "expiresIn": 3600}Showing images in the UI
Section titled “Showing images in the UI”After loading rows that reference stored files by objectId:
- For each file, call the download API or SDK
getDownloadUrl(objectId). - Use the returned
downloadUrlas<img src>or download link. - For lists, resolve download URLs in parallel (
Promise.all) for speed.
Listing files
Section titled “Listing files”GET /storage/{app_id}/objectsAuthorization: Bearer {token}Returns an array of objects with id, filename, content_type, size_bytes, and created_at.
Storage limits
Section titled “Storage limits”| Limit | Default |
|---|---|
| Max file size | 10 MB per file |
| Total storage | 1 GB per app |
| Allowed content types | All types (configurable) |
Access control
Section titled “Access control”- Service key: Full access to all files. Uploads have no user association.
- End-users: Can only see and manage their own files. Uploads are automatically associated with the authenticated user.
URL expiration
Section titled “URL expiration”- Upload URLs expire after 5 minutes
- Download URLs expire after 1 hour
SDK usage
Section titled “SDK usage”const { data } = await butterbase.storage.upload(file);const { data: url } = await butterbase.storage.getDownloadUrl(objectId);const { data: objects } = await butterbase.storage.list();await butterbase.storage.delete(objectId);