File Storage (R2)
Quickback provides built-in file storage using Cloudflare R2, with role-based access control and a dedicated files worker for serving.
Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ Your Application │
│ │
│ Upload/Manage Files Serve Files │
│ ───────────────────── ────────────── │
│ api.yourdomain.com files.yourdomain.com │
│ /storage/v1/* /* │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ API Worker │ │ Files Worker │ │
│ │ │ │ │ │
│ │ POST /bucket │ │ GET /public/* │ │
│ │ GET /bucket │ │ → No auth │ │
│ │ POST /object/* │ │ │ │
│ │ DELETE /object/* │ │ GET /* │ │
│ │ │ │ → Session + RBAC │ │
│ └──────────┬──────────┘ └──────────┬──────────┘ │
│ │ │ │
│ └───────────┬───────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ R2 Bucket │ │
│ │ quickback-files │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘Enabling File Storage
Add the fileStorage provider to your quickback.config.ts:
export default {
name: 'my-app',
providers: {
runtime: { name: 'cloudflare' },
database: { name: 'cloudflare-d1' },
auth: { name: 'better-auth' },
fileStorage: {
name: 'cloudflare-r2',
config: {
binding: 'R2_BUCKET',
bucketName: 'my-app-files',
filesBinding: 'FILES_DB',
maxFileSize: 10 * 1024 * 1024, // 10MB
allowedTypes: ['image/jpeg', 'image/png', 'image/webp'],
},
},
},
};API Endpoints
Storage API (api.yourdomain.com/storage/v1)
| Method | Endpoint | Description |
|---|---|---|
| POST | /bucket | Create a bucket |
| GET | /bucket | List buckets |
| GET | /bucket/:name | Get bucket info |
| DELETE | /bucket/:name | Delete bucket (must be empty) |
| POST | /object/:bucket/*path | Upload file |
| GET | /object/:bucket/*path | Download file |
| HEAD | /object/:bucket/*path | Get file metadata |
| DELETE | /object/:bucket/*path | Delete file (soft delete) |
| GET | /object | List objects |
| POST | /url | Get file URL for serving |
Files Worker (files.yourdomain.com)
| Method | Path | Auth Required |
|---|---|---|
| GET/HEAD | /public/* | No |
| GET/HEAD | /* | Yes (session + RBAC) |
Buckets
Buckets organize files and define access control policies.
Creating a Bucket
POST /storage/v1/bucket
{
"name": "avatars",
"readScope": "organization",
"writeScope": "organization",
"readRoles": ["admin", "member"],
"writeRoles": ["admin"],
"deleteRoles": ["admin"]
}Scope Options
| Scope | Read Behavior | Write Behavior |
|---|---|---|
public | Anyone can read | N/A |
organization | Org members only | Org members only |
user | Owner only | Owner only |
Role-Based Access
You can restrict operations to specific roles:
{
"readRoles": ["admin", "member"],
"writeRoles": ["admin", "editor"],
"deleteRoles": ["admin"]
}An empty array [] means no role restriction (all authenticated users).
Uploading Files
POST /storage/v1/object/avatars/profile.jpg
Content-Type: image/jpeg
Content-Length: 12345
<binary data>Response:
{
"id": "obj_123",
"key": "org_abc/avatars/profile.jpg",
"bucket": "avatars",
"name": "profile.jpg",
"size": 12345,
"mimeType": "image/jpeg",
"readScope": "organization"
}Serving Files
Public Files
Files in buckets with readScope: "public" are stored with a public/ prefix:
https://files.yourdomain.com/public/org_abc/avatars/logo.pngNo authentication required.
Private Files
Private files require a valid Better Auth session cookie:
https://files.yourdomain.com/org_abc/documents/report.pdfThe files worker:
- Validates the session token
- Checks the user's organization matches
- Verifies role permissions (if
readRolesconfigured) - Serves the file or returns 403
Generated Files
When file storage is configured, the compiler generates:
| File | Purpose |
|---|---|
src/routes/storage.ts | Storage API routes |
src/files.ts | Files database schema (buckets, objects) |
cloudflare-workers/files/index.ts | Files worker for serving |
cloudflare-workers/files/wrangler.toml | Files worker config |
Deployment
After compiling:
-
Create the R2 bucket:
wrangler r2 bucket create my-app-files -
Create the files database:
wrangler d1 create my-app-files -
Run migrations:
wrangler d1 migrations apply my-app-files --local wrangler d1 migrations apply my-app-files --remote -
Deploy the API:
wrangler deploy -
Deploy the files worker:
cd cloudflare-workers/files wrangler deploy
Configuration Reference
| Option | Type | Default | Description |
|---|---|---|---|
binding | string | R2_BUCKET | R2 bucket binding name |
bucketName | string | Required | R2 bucket name |
filesBinding | string | FILES_DB | Files metadata D1 binding |
maxFileSize | number | 10MB | Max upload size in bytes |
allowedTypes | string[] | Images only | Allowed MIME types |
publicDomain | string | - | Custom domain for files worker |
Security Model
- Upload security: Enforced by API worker (auth middleware, org check, role check)
- Serve security: Enforced by files worker (session validation, metadata RBAC)
- Soft deletes: Files are marked deleted in metadata but retained in R2
- Tenant isolation: All files are prefixed with organization ID