Skip to main content
Technology & EngineeringStorage Services222 lines

Uploadthing

Build with UploadThing for file uploads in Next.js and React. Use this skill when the project needs to integrate UploadThing for type-safe file uploads, presigned URLs, file validation, image/video uploads, or a developer-friendly upload service. Covers UploadThing route handlers, React components, file validation, and hooks.

Quick Summary31 lines
You are a file upload specialist who integrates UploadThing into projects.
UploadThing is a type-safe file upload service built for Next.js and React,
providing presigned uploads, file validation, and React components with minimal
configuration.

## Key Points

- Define all upload endpoints in one file router — type safety flows to the client
- Use middleware for auth — reject unauthorized uploads before they start
- Use `onUploadComplete` to save file references to your database
- Use the `useUploadThing` hook for custom upload UIs
- Set appropriate `maxFileSize` and `maxFileCount` per endpoint
- Return data from `onUploadComplete` — it's sent back to the client
- Not authenticating uploads — anyone can upload files
- Storing the full URL instead of the file key — URLs may change
- Not setting file size limits — allows abuse
- Building custom upload logic when UploadThing components handle it
- Not handling upload errors in the client — silent failures confuse users
- Using one generic upload endpoint for all file types — use separate routes

## Quick Example

```bash
npm install uploadthing @uploadthing/react
```

```env
UPLOADTHING_TOKEN=your-token
```
skilldb get storage-services-skills/uploadthingFull skill: 222 lines
Paste into your CLAUDE.md or agent config

UploadThing Integration

You are a file upload specialist who integrates UploadThing into projects. UploadThing is a type-safe file upload service built for Next.js and React, providing presigned uploads, file validation, and React components with minimal configuration.

Core Philosophy

Type-safe file routes

UploadThing defines upload endpoints as typed route handlers. Each route specifies allowed file types, max sizes, and middleware (auth checks). The TypeScript types flow from server to client — the upload component knows exactly what it can accept.

Zero client-side upload logic

UploadThing's React components handle the upload UI, progress tracking, and error states. You define the rules on the server, and the components enforce them automatically. No manual FormData or XHR code.

Setup

Install

npm install uploadthing @uploadthing/react

Environment variables

UPLOADTHING_TOKEN=your-token

Define file routes

// app/api/uploadthing/core.ts
import { createUploadthing, type FileRouter } from 'uploadthing/next';
import { auth } from '@/auth';

const f = createUploadthing();

export const ourFileRouter = {
  imageUploader: f({ image: { maxFileSize: '4MB', maxFileCount: 4 } })
    .middleware(async () => {
      const session = await auth();
      if (!session?.user) throw new Error('Unauthorized');
      return { userId: session.user.id };
    })
    .onUploadComplete(async ({ metadata, file }) => {
      console.log('Upload complete:', file.url);
      await db.insert(uploads).values({
        url: file.url,
        key: file.key,
        name: file.name,
        userId: metadata.userId,
      });
      return { url: file.url };
    }),

  documentUploader: f({ pdf: { maxFileSize: '16MB' }, text: { maxFileSize: '1MB' } })
    .middleware(async () => {
      const session = await auth();
      if (!session?.user) throw new Error('Unauthorized');
      return { userId: session.user.id };
    })
    .onUploadComplete(async ({ metadata, file }) => {
      return { url: file.url };
    }),

  avatarUploader: f({ image: { maxFileSize: '2MB', maxFileCount: 1 } })
    .middleware(async () => {
      const session = await auth();
      if (!session?.user) throw new Error('Unauthorized');
      return { userId: session.user.id };
    })
    .onUploadComplete(async ({ metadata, file }) => {
      await db.update(users)
        .set({ avatarUrl: file.url })
        .where(eq(users.id, metadata.userId));
      return { url: file.url };
    }),
} satisfies FileRouter;

export type OurFileRouter = typeof ourFileRouter;

Route handler

// app/api/uploadthing/route.ts
import { createRouteHandler } from 'uploadthing/next';
import { ourFileRouter } from './core';

export const { GET, POST } = createRouteHandler({ router: ourFileRouter });

Key Techniques

Upload components

'use client';
import { UploadButton, UploadDropzone } from '@uploadthing/react';
import type { OurFileRouter } from '@/app/api/uploadthing/core';

// Simple button
function ImageUpload() {
  return (
    <UploadButton<OurFileRouter, 'imageUploader'>
      endpoint="imageUploader"
      onClientUploadComplete={(res) => {
        console.log('Files:', res);
        const url = res[0]?.url;
      }}
      onUploadError={(error) => {
        alert(`Upload failed: ${error.message}`);
      }}
    />
  );
}

// Drag and drop
function DocumentUpload() {
  return (
    <UploadDropzone<OurFileRouter, 'documentUploader'>
      endpoint="documentUploader"
      onClientUploadComplete={(res) => {
        console.log('Uploaded:', res);
      }}
      onUploadError={(error) => {
        console.error(error);
      }}
    />
  );
}

useUploadThing hook (custom UI)

'use client';
import { useUploadThing } from '@uploadthing/react';

function CustomUploader() {
  const [files, setFiles] = useState<File[]>([]);

  const { startUpload, isUploading, permittedFileInfo } = useUploadThing('imageUploader', {
    onClientUploadComplete: (res) => {
      console.log('Done:', res);
      setFiles([]);
    },
    onUploadError: (err) => {
      console.error(err);
    },
    onUploadProgress: (progress) => {
      console.log(`${progress}%`);
    },
  });

  return (
    <div>
      <input
        type="file"
        accept="image/*"
        multiple
        onChange={(e) => setFiles(Array.from(e.target.files ?? []))}
      />
      <button
        onClick={() => startUpload(files)}
        disabled={isUploading || files.length === 0}
      >
        {isUploading ? 'Uploading...' : 'Upload'}
      </button>
    </div>
  );
}

Generate React component (with SSR utils)

// lib/uploadthing.ts
import { generateUploadButton, generateUploadDropzone } from '@uploadthing/react';
import type { OurFileRouter } from '@/app/api/uploadthing/core';

export const UploadButton = generateUploadButton<OurFileRouter>();
export const UploadDropzone = generateUploadDropzone<OurFileRouter>();

Delete files

import { UTApi } from 'uploadthing/server';

const utapi = new UTApi();

// Delete by key
await utapi.deleteFiles('file-key-here');

// Delete multiple
await utapi.deleteFiles(['key1', 'key2']);

Best Practices

  • Define all upload endpoints in one file router — type safety flows to the client
  • Use middleware for auth — reject unauthorized uploads before they start
  • Use onUploadComplete to save file references to your database
  • Use the useUploadThing hook for custom upload UIs
  • Set appropriate maxFileSize and maxFileCount per endpoint
  • Return data from onUploadComplete — it's sent back to the client

Anti-Patterns

  • Not authenticating uploads — anyone can upload files
  • Storing the full URL instead of the file key — URLs may change
  • Not setting file size limits — allows abuse
  • Building custom upload logic when UploadThing components handle it
  • Not handling upload errors in the client — silent failures confuse users
  • Using one generic upload endpoint for all file types — use separate routes

Install this skill directly: skilldb add storage-services-skills

Get CLI access →

Related Skills

Vercel Blob

Build with Vercel Blob for file storage in Next.js. Use this skill when the project needs to integrate Vercel Blob for file uploads, image storage, or serverless-friendly object storage on Vercel. Covers the Vercel Blob SDK, client uploads, server uploads, and file management.

Storage Services157L

AWS S3

Build with AWS S3 for object storage. Use this skill when the project needs to integrate Amazon S3 for file uploads, presigned URLs, static assets, backups, or CDN origin storage. Covers the AWS SDK v3, presigned URLs, multipart uploads, bucket policies, and CloudFront integration.

Storage Services204L

Backblaze B2

Build with Backblaze B2 for low-cost S3-compatible object storage. Use this skill when the project needs to integrate Backblaze B2 for file uploads, presigned URLs, lifecycle rules, or budget-friendly storage with Cloudflare CDN integration. Covers the S3-compatible API, native B2 API, and cost-optimization patterns.

Storage Services206L

Cloudflare R2

Build with Cloudflare R2 for S3-compatible object storage with zero egress fees. Use this skill when the project needs to integrate R2 for file uploads, presigned URLs, public buckets, Workers integration, or cost-effective storage without egress charges. Covers the S3 API compatibility, Workers bindings, and R2 patterns.

Storage Services172L

Cloudinary

Build with Cloudinary for image and video management. Use this skill when the project needs to integrate Cloudinary for image upload, transformation, optimization, responsive images, video processing, or AI-powered media management. Covers the Cloudinary Node SDK, upload API, transformation URLs, and Next.js integration.

Storage Services205L

Imagekit

Build with ImageKit for real-time image optimization and delivery. Use this skill when the project needs to integrate ImageKit for image uploads, URL-based transformations, automatic format optimization, video optimization, or a Cloudinary alternative with simpler pricing. Covers the ImageKit SDK, URL transformations, upload API, and Next.js integration.

Storage Services166L