Storage · modules/storage
Adapter scaffoldUploadThing,
contract-first.
UploadThing plugs into the same IStorageProvider port as the S3, local and Supabase adapters. The slot is a typed scaffold today — the interface is committed, so completing it is a self-contained change that leaves the rest of the app untouched.
What ships, and what you complete
Honesty first: the UploadThing adapter is a scaffold. The boilerplate baseline does not ship the UploadThing SDK as a dependency, so presignUpload / presignDownload return a typed "not yet implemented" until you drop it in. The factory soft-falls back to the null storage provider (with a warning) when UPLOADTHING_TOKEN is set but the SDK is absent, so nothing crashes at boot. The value here is the same as every other adapter slot: the IStorageProvider port is locked, so when you wire UploadThing you implement against a shape the rest of the codebase already consumes — no ripple. If you want file storage today, the S3 adapter is fully implemented and speaks to AWS, R2 and MinIO.
The storage port it plugs into
Typed scaffold
The adapter implements IStorageProvider; each method returns the same Result types as the wired providers, so the contract is the commitment.
Soft fallback
When STORAGE_PROVIDER=uploadthing but the token/SDK is missing, the factory logs a warning and returns the null provider instead of throwing.
Wired alternative
Need storage now? STORAGE_PROVIDER=s3 is production-ready and works with AWS S3, Cloudflare R2 and MinIO.
One-file swap
Because every provider shares the port, moving from S3 to UploadThing later is a composition-root change, not an app rewrite.
Configuration reference
Environment variables
STORAGE_PROVIDERrequiredSet to uploadthing
UPLOADTHING_TOKENrequiredUPLOADTHING_APP_IDoptional
Where it lives
apps/server/src/modules/storage/infrastructure/providers/uploadthing-storage-provider.tsapps/server/src/modules/storage/infrastructure/providers/index.ts
Read more
Storage behind one contract.
Start on S3 today and swap to UploadThing later without rewriting your app — the port stays the same.