| name | better-upload |
| description | Use when working with better-upload, adding file uploads to a React or Next.js app using pre-signed S3 URLs, configuring upload routes or callbacks (onBeforeUpload, onAfterSignedUrl, RejectUpload), setting up S3 clients (AWS S3, Cloudflare R2, MinIO, Backblaze, DigitalOcean Spaces, Tigris, Wasabi), using useUploadFiles or useUploadFile hooks, implementing multipart uploads, managing upload progress or abort, using uploadFile/uploadFiles imperatively, integrating with TanStack Query or React Hook Form, working with S3 helpers (deleteObject, presignGetObject, moveObject), or using UploadDropzone/UploadButton components. |
Better Upload
Better Upload enables direct file uploads from the browser to any S3-compatible storage using pre-signed URLs. The server generates temporary signed URLs; the client uploads directly to S3 without proxying through your server.
Architecture
Browser → (1) POST /api/upload → Your Server (@better-upload/server)
↓ generates pre-signed URL
Browser → (2) PUT signed URL → S3 Bucket (AWS, R2, MinIO, etc.)
Packages
| Package | Role |
|---|
@better-upload/server | Router, routes, S3 clients, object helpers |
@better-upload/client | React hooks (useUploadFiles, useUploadFile), imperative upload functions |
Installation
npm i @better-upload/server @better-upload/client
Quick Start
Server — Next.js App Router
import { route, type Router } from '@better-upload/server';
import { toRouteHandler } from '@better-upload/server/adapters/next';
import { cloudflare } from '@better-upload/server/clients';
const router: Router = {
client: cloudflare(),
bucketName: process.env.AWS_BUCKET_NAME!,
routes: {
images: route({
multipleFiles: true,
fileTypes: ['image/*'],
maxFileSize: 1024 * 1024 * 5,
maxFiles: 4,
}),
},
};
export const { POST } = toRouteHandler(router);
Client — React hook
'use client';
import { useUploadFiles } from '@better-upload/client';
export function Uploader() {
const { upload, isPending, uploadedFiles } = useUploadFiles({
route: 'images',
});
return (
<input
type="file"
multiple
disabled={isPending}
onChange={(e) => {
if (e.target.files) upload(e.target.files);
}}
/>
);
}
The default API endpoint is /api/upload. Override with api: '/your/path' if needed.
Reference Files
Read only the file(s) relevant to the current task.
Router type, route() options (single and multiple files), onBeforeUpload / onAfterSignedUrl callbacks, RejectUpload, multipart uploads, adapters (Next.js, Node.js), all S3 clients with their env vars.
useUploadFiles and useUploadFile hooks — all options, return values, and events (onBeforeUpload, onUploadBegin, onUploadProgress, onUploadComplete, onUploadFail, onError, onUploadSettle). Imperative functions (uploadFile, uploadFiles). Pre-built shadcn components.
Server-side S3 helpers from @better-upload/server/helpers: presignGetObject, getObject, headObject, listObjectsV2, putObject, deleteObject, deleteObjects, moveObject, copyObject, object tagging, and low-level multipart helpers. Client-side: formatBytes.
Examples
Focused, copy-paste examples for common patterns.