Overview
S3-compatible file uploads with presigned URLs via @repo/storage.
TurboJet includes a built-in file storage layer for uploading images, documents, and other assets to the cloud. It works with any S3-compatible provider — AWS S3, Cloudflare R2, DigitalOcean Spaces, MinIO, and more. The docs and .env.example default to Cloudflare R2.
Helpers live in @repo/storage. Authenticated HTTP endpoints are mounted at /api/storage on the marketing app.
No upload UI yet
There is no file-upload UI in apps/marketing today. The package, Prisma
File model, and Hono routes are ready for a client you add yourself.
Uploading in a serverless architecture
Next.js route handlers (and Hono behind them) have limits on serverless platforms — execution time and request body size. Sending large files through your API is a poor fit.
TurboJet uses presigned URLs instead:
- The client asks the API for a short-lived upload URL (auth and validation stay on your server).
- The client uploads the file directly to the storage provider with
PUT. - The client confirms the upload so the API can mark the Prisma
Filerow asREADY.
Credentials never reach the browser. You avoid serverless payload limits while still enforcing who can upload what.
Guides
Connect to S3 storage
Environment variables, CORS, and private buckets.
Upload files
Presigned upload, confirm, and client sketch.
Access stored files
List, signed download URLs, public URLs, and delete.
Storage provider guides
Cloudflare R2
Zero egress fees, S3-compatible — the recommended default.
AWS S3
The original S3 API and the most widely used object store.
Frequently asked questions
Which storage providers work?
Any provider that implements the S3 API. Configure it with S3_ENDPOINT, credentials, and S3_BUCKET. Local Docker Compose does not ship MinIO or SeaweedFS — use a cloud bucket (or run your own S3-compatible service) and point the env vars at it.
What is the maximum file upload size?
The API validates uploads at 10 MB and allows jpeg, png, gif, webp, PDF, and plain text (packages/validations/src/storage/storage.ts). Because the file body goes to the provider via a presigned URL, serverless body limits do not apply to the binary itself. Raise or lower the Zod limit if your product needs different constraints.
How do file uploads work?
GET /api/storage/upload creates a PENDING file and returns a presigned PUT URL. The client uploads to that URL, then calls POST /api/storage/confirm to mark the file READY. See Upload files.
Do I need to configure CORS?
Yes. Browser uploads are cross-origin to the storage host. Allow your marketing origin (NEXT_PUBLIC_MARKETING_URL) for PUT, GET, and HEAD, and expose headers your client needs (at least Content-Type). Details are in Setup and the provider guides.