Storage
Status: Implemented (Milestone 8)
Bakend stores uploaded files on the local filesystem with metadata in SQLite. Files can be public (downloadable without auth) or protected (owner or admin only).
Configuration
{
"storage": "./storage"
}
Env override: BAKEND_STORAGE
Upload
Uploads require authentication. Send a multipart form request:
curl -X POST http://localhost:8080/api/storage/upload \
-H 'Authorization: Bearer <token>' \
-F 'file=@photo.png' \
-F 'visibility=public'
Fields:
file(required) — the file bytesvisibility(optional) —publicorprotected(default:protected)
Response (201):
{
"id": "fil_...",
"filename": "photo.png",
"mimeType": "image/png",
"size": 12345,
"visibility": "public",
"userId": "usr_...",
"createdAt": "2026-01-01T00:00:00.000Z"
}
Max file size: 10 MB (V1 hardcoded limit).
Download
curl http://localhost:8080/api/storage/fil_...
- Public files: no auth required
- Protected files:
Authorization: Bearer <token>required (owner or admin)
Delete
curl -X DELETE http://localhost:8080/api/storage/fil_... \
-H 'Authorization: Bearer <token>'
Only the file owner or an admin can delete.
Collection File Fields
Define a file field in your collection:
{
"name": "attachments",
"fields": [
{ "name": "title", "type": "string", "required": true },
{ "name": "file_id", "type": "file", "required": true }
]
}
Workflow:
- Upload via
POST /api/storage/upload - Create a record referencing the returned
idin thefile_idfield
Record validation checks that the file ID exists in _files. Deleting a record does not delete the file automatically.
Remove orphan files not referenced by any record:
bak storage prune
Events
storage.uploaded— metadata payloadstorage.deleted—{ id, userId }
Functions and Jobs
export default async ({ storage }) => {
const file = await storage.get("fil_...");
await storage.delete("fil_...");
};