Media Uploader Kit
Presigned direct uploads, finalize + accessUrl, and audit-only ORM rows. UI kit + API adapters for S3, Azure Blob, GCS, Cloudinary (images/videos), and Dropbox.
Quick reference
CLI commands
# UI
npx @fivfold/ui add media-uploader
# API (pick storage provider)
npx @fivfold/api add media-uploader --framework=nestjs --orm=typeorm --provider=s3
npx @fivfold/ui add media-uploader --dry-run
npx @fivfold/api add media-uploader --dry-run --provider=azure-blobOverview
The Media Uploader kit follows the standard three-step flow: your backend issues a presign response and records a pending audit row; the browser uploads bytes directly to object storage; then the client calls finalize so the API can set completed or failed with timestamps and return accessUrl only when the storage upload succeeded.
Demo
Interactive preview: simulated presign/finalize; transport uses simulateTransport — no real cloud upload.
Interactive demo: presign and finalize are simulated; transport uses simulateTransport so files are not uploaded anywhere.
Guide
Choose Frontend and Storage in the sidebar for matching connection and CORS notes.
1Installation
npx @fivfold/ui add media-uploader
# Dry run
npx @fivfold/ui add media-uploader --dry-runRun npx @fivfold/ui init first if you have not. Output is written under your configured kits path (default @/components/ui/kits/media-uploader).
2Generated file structure
kits/media-uploader/
├── index.tsx # exports MediaUploaderKit, types, upload helpers
├── media-uploader.tsx # main UI (drag-drop, progress, confirm)
├── types.ts # DirectUploadInstruction, presign/finalize types
└── upload-client.ts # XHR upload + concurrency helper3Import and usage
Wire presign to POST /media-uploader/presign and finalizeUpload to POST /media-uploader/finalize on your API. On success, finalizeUpload returns accessUrl — store it on your own models (the kit does not persist app data beyond calling your API).
import { MediaUploaderKit } from "@/components/ui/kits/media-uploader";
export function UploadPage() {
return (
<MediaUploaderKit
presign={async (file) => {
const res = await fetch("/api/media-uploader/presign", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
fileName: file.name,
mimeType: file.type || "application/octet-stream",
sizeBytes: file.size,
}),
});
if (!res.ok) throw new Error(await res.text());
return res.json();
}}
finalizeUpload={async (payload) => {
const res = await fetch("/api/media-uploader/finalize", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
return res.json();
}}
uploadConcurrency={3}
onUploadSuccess={(_, r) => console.log("URL:", r.accessUrl)}
/>
);
}4Props reference
| Prop | Description |
|---|---|
| presign | Returns uploadSessionId + upload instruction from your API. |
| finalizeUpload | Called after storage upload succeeds or fails. Success response must include accessUrl. |
| uploadConcurrency | Parallel uploads (default 3). Use 1 for sequential. |
| simulateTransport | Docs-only: skips real HTTP upload to storage (used on this site's demo). |
| requireConfirm | Default true — user confirms before upload starts. |
| maxSizeBytes / warnSizeBytes | Hard cap vs warning threshold for file size UI. |
5Integration with backend
Flow: presign creates a pending audit row and returns a direct upload instruction → browser uploads bytes to the cloud → finalize marks the audit completed or failed and returns accessUrl only on success.
You own the wiring
The step-by-step guides, environment variables, and dev-server proxy or rewrite examples are suggestions—not a mandated layout. You should read the generated files, your existing routes and auth, and your deployment topology, then choose URLs, origins, and headers that match your app. We encourage verifying every connection yourself before shipping.
Step-by-step: Media Uploader UI → API
Examples match the Frontend choice in the stack sidebar (Next.js). Your mount paths may differ; adjust prefixes to match how you register routes and proxies.
Chat kit: follow the ordered full-stack checklist for dev user middleware, /socket.io proxying, headers, and the integration host.
Next.js → API
Prefer rewrites in next.config.ts so browser code can use same-origin paths like /api/media-uploader/... while the dev server forwards to your backend. Alternatively set NEXT_PUBLIC_API_URL and call that base from client code.
// next.config.ts — example rewrite to API on another port
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'http://localhost:3001/:path*', // your Nest/Express port
},
];
},
};
export default nextConfig;If the app and API both use port 3000, run the API on a different port (e.g. 3001) or use a monorepo BFF pattern.
API → browser (CORS)
Allow your UI's dev origin on the API. For Next.js that is typically http://localhost:3000 (plus 127.0.0.1 if you use it).
Production
https://api.example.com/media-uploader) and lock CORS to known web origins — not *.6Third-party integrations
Object storage is configured on the API side (S3, Azure, GCS, Cloudinary, Dropbox). See the API tab for env vars and CORS.
7shadcn/ui primitives
| Primitive | Usage |
|---|---|
| Button, Card, Progress | Layout, confirm, progress bars |
| ScrollArea, Badge | File list, extension labels |
8Additional
- Use
uploadToDirectStoragefrom the kit if you build a custom flow; it supportshttp-putandpost-multipartinstructions. - Cloudinary adapter on the API only allows image/* and video/* MIME types.
