Storage · modules/storage
Production-ready adapterS3-compatible
object storage.
The storage module owns file uploads behind an IStorageProvider port. The S3 adapter runs on the AWS SDK and — because AWS, Cloudflare R2 and MinIO speak the same protocol — the same code serves all three, differing only in a few env vars.
AWS, R2 or MinIO — same adapter
The only differences between AWS S3, Cloudflare R2 and MinIO are the endpoint, path-style addressing, and the public base URL — all driven by env. Point the same S3StorageProvider at any of them.
1 STORAGE_PROVIDER=s3
2 STORAGE_S3_BUCKET=my-bucket
3 STORAGE_S3_REGION=auto
4 STORAGE_S3_ACCESS_KEY_ID=...
5 STORAGE_S3_SECRET_ACCESS_KEY=...
6 # R2 / MinIO only:
7 STORAGE_S3_ENDPOINT=https://<accountid>.r2.cloudflarestorage.com
8 STORAGE_S3_FORCE_PATH_STYLE=true # MinIO
What ships
Presigned URLs
Uploads and downloads use presigned URLs via @aws-sdk/s3-request-presigner — bytes never proxy through your server.
Content-addressed keys
Avatar keys are avatars/{userId}-{sha256:16}.{ext}, so the immutable one-year cache header is safe across re-uploads — a new file writes a new key.
One port, five providers
STORAGE_PROVIDER selects null / s3 / local / uploadthing / supabase behind the same IStorageProvider interface.
Public or signed
Set a public base URL (CDN / R2 public bucket) or let getPublicUrl fall back to a long-lived signed URL.
Configuration reference
Environment variables
STORAGE_PROVIDERrequiredSet to s3
STORAGE_S3_BUCKETrequiredSTORAGE_S3_REGIONrequiredSTORAGE_S3_ACCESS_KEY_IDrequiredSTORAGE_S3_SECRET_ACCESS_KEYrequiredSTORAGE_S3_ENDPOINToptionalR2 / MinIO / CEPH endpoint
STORAGE_S3_FORCE_PATH_STYLEoptionaltrue for MinIO
Where it lives
apps/server/src/modules/storage/infrastructure/providers/s3-storage-provider.tsapps/server/src/modules/storage/infrastructure/providers/index.ts
One storage adapter, any S3 backend.
Ship uploads on AWS S3, Cloudflare R2 or MinIO without touching your application code.