ShipNext

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.ts

Switch 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/abort

If 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.

On this page