Add A Storage Provider
Extend or replace the current S3-compatible storage implementation.
Storage is currently implemented in src/modules/storage rather than a separate src/integrations/storage package. The default provider is S3-compatible, so AWS S3, Cloudflare R2, and similar services often require only environment variable changes.
Confirm whether a new provider is needed
If the service is S3-compatible, start by changing:
S3_ENDPOINT=
S3_REGION=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_BUCKET_NAME=
S3_PUBLIC_URL=Browser uploads require presigned URLs and bucket CORS. No code change is needed when the provider supports S3 Multipart Upload.
Implement the shared interface
For non-S3 providers, implement the same interface from src/modules/storage/types.ts:
export interface StorageProvider {
upload(key: string, body: Buffer | File, options?: UploadOptions): Promise<UploadResult>;
delete(key: string): Promise<void>;
getUrl(key: string): Promise<string>;
}For example:
src/modules/storage/providers/blob.tsSwitch the exported provider
Callers import:
import { storage } from "@/modules/storage";Keep that API stable by adding provider selection in provider.ts or exporting the selected implementation from index.ts.
Replace browser upload APIs if needed
Current browser upload routes depend on S3 Multipart Upload:
POST /api/storage/presigned-url
POST /api/storage/presigned-url/complete
POST /api/storage/presigned-url/abortIf the new provider does not support that flow, update:
src/modules/storage/services/multipart-upload.service.ts
src/modules/storage/services/browser-upload.service.ts
app/api/storage/presigned-url/*Keep file records stable
Continue writing storage_files with userId, objectKey, fileName, fileSize, and expiresAt so usage tracking and avatar URL resolution continue to work.
For R2-to-S3 style changes, prefer environment variables and bucket CORS. Add a provider only when the API shape is genuinely different.