Storage
File uploads with local filesystem or S3
Upload
curl -X POST /api/files -F "[email protected]"
# 201 { fileId: "abc123.jpg", url: "/api/files/abc123.jpg" }Uploads require an authenticated session by default. File ownership is tracked in an internal metadata table.
Retrieve / delete
curl /api/files/abc123.jpg
curl -X DELETE /api/files/abc123.jpg # 204 — owner only by defaultAccess rules
bunderstack({
schema,
storage: {
local: './uploads',
defaultBucket: 'files',
buckets: {
files: {
access: {
create: 'authenticated', // default
get: 'public', // default
delete: 'owner', // default
},
},
},
},
})Local storage
bunderstack({ schema, storage: { local: './uploads' } })S3 / R2 / MinIO
bunderstack({ schema, storage: { s3: true } })
# Set S3_BUCKET, S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY in .env
# For R2/MinIO also set S3_ENDPOINTbunderstack({
schema,
storage: {
local: './uploads',
defaultBucket: 'files',
buckets: {
files: {
upload: {
accept: ['image/jpeg', 'image/png', 'image/webp'],
maxSize: '5mb',
},
},
},
},
})Programmatic URLs (app.storage.getUrl)
Use app.storage.getUrl to programmatically resolve presigned S3 download URLs in production or local proxy URLs in development:
// Programmatically get download URL for a file key
const downloadUrl = await app.storage.getUrl('resumes/user_123/cv.pdf', {
expiresIn: 3600,
})Server-side uploads (app.storage.upload)
Use app.storage.upload (or context.storage.upload in jobs and API procedures)
to upload server-generated files such as PDFs and exports. It registers the
storage metadata automatically, so the file is available through the generated
download procedure and HTTP route:
await app.storage.upload(
'adaptations/123/resume.pdf',
pdfBytes,
'application/pdf',
{ filename: 'resume.pdf', ownerId: user.id },
)Nested keys (like adaptations/123/resume.pdf) are fully supported by GET /api/files/:bucket/* and DELETE /api/files/:bucket/*.