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

Bash
# 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-blob

Overview

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.

Media uploader

Drag and drop files here, or

Guide

Choose Frontend and Storage in the sidebar for matching connection and CORS notes.

1Installation

Bash
npx @fivfold/ui add media-uploader # Dry run npx @fivfold/ui add media-uploader --dry-run

Run 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

Plain Text
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 helper

3Import 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).

TSX
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

PropDescription
presignReturns uploadSessionId + upload instruction from your API.
finalizeUploadCalled after storage upload succeeds or fails. Success response must include accessUrl.
uploadConcurrencyParallel uploads (default 3). Use 1 for sequential.
simulateTransportDocs-only: skips real HTTP upload to storage (used on this site's demo).
requireConfirmDefault true — user confirms before upload starts.
maxSizeBytes / warnSizeBytesHard 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
// 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

Use your real API base (e.g. 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

PrimitiveUsage
Button, Card, ProgressLayout, confirm, progress bars
ScrollArea, BadgeFile list, extension labels

8Additional

  • Use uploadToDirectStorage from the kit if you build a custom flow; it supports http-put and post-multipart instructions.
  • Cloudinary adapter on the API only allows image/* and video/* MIME types.

Search docs

Search documentation by title or keywords