Introduction
UploadThing is an open-source file upload solution specifically designed for Next.js applications. It provides developers with a type-safe, efficient way to handle file uploads while offering features like file validation, transformation, and direct integration with popular frameworks.
Technical Overview
At its core, UploadThing consists of three main components:
- Server-side file router
- Client-side components and hooks
- Type-safe API endpoints
Installation and Basic Setup
First, install the required packages:
npm install uploadthing @uploadthing/react
Create a file router (typically in app/api/uploadthing/core.ts):
import { createUploadthing, type FileRouter } from "uploadthing/server";
const f = createUploadthing();
export const uploadRouter = {
// Example "profile picture upload" route - these can be named whatever you want!
profilePicture: f(["image"])
.middleware(({ req }) => auth(req))
.onUploadComplete((data) => console.log("file", data)),
// This route takes an attached image OR video
messageAttachment: f(["image", "video"])
.middleware(({ req }) => auth(req))
.onUploadComplete((data) => console.log("file", data)),
// Takes exactly ONE image up to 2MB
strictImageAttachment: f({
image: { maxFileSize: "2MB", maxFileCount: 1, minFileCount: 1 },
})
.middleware(({ req }) => auth(req))
.onUploadComplete((data) => console.log("file", data)),
// Takes up to 4 2mb images and/or 1 256mb video
mediaPost: f({
image: { maxFileSize: "2MB", maxFileCount: 4 },
video: { maxFileSize: "256MB", maxFileCount: 1 },
})
.middleware(({ req }) => auth(req))
.onUploadComplete((data) => console.log("file", data)),
// Takes up to 4 2mb images, and the client will not resolve
// the upload until the `onUploadComplete` resolved.
withAwaitedServerData: f(
{ image: { maxFileSize: "2MB", maxFileCount: 4 } },
{ awaitServerData: true },
)
.middleware(({ req }) => auth(req))
.onUploadComplete((data) => {
return { foo: "bar" as const };
}),
} satisfies FileRouter;
export type UploadRouter = typeof uploadRouter;
These are the routes you create with the helper instantiated by createUploadthing. Think of them as the "endpoints" for what your users can upload. An object with file routes constructs a file router where the keys (slugs) in the object are the names of your endpoints.
Route Config:
The f function takes two arguments. The first can be an array of FileType, or a record mapping each FileType with a route config. The route config allow more granular control, for example what files can be uploaded and how many of them can be uploaded for a given upload. The array syntax will fallback to applying the defaults to all file types.
A FileType can be any valid )
ingestUrl: UploadThing Ingest API URL
File Operations
Upload Files
import { utapi } from "~/server/uploadthing";
async function uploadFiles(formData: FormData) {
"use server";
const files = formData.getAll("files");
const response = await utapi.uploadFiles(files);
}
Upload Files from URL
const fileUrl = "https://test.com/some.png";
const uploadedFile = await utapi.uploadFilesFromUrl(fileUrl);
const fileUrls = ["https://test.com/some.png", "https://test.com/some2.png"];
const uploadedFiles = await utapi.uploadFilesFromUrl(fileUrls);
Delete Files
await utapi.deleteFiles("2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg");
await utapi.deleteFiles([
"2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg",
"1649353b-04ea-48a2-9db7-31de7f562c8d_image2.jpg",
]);
List Files
const files = await utapi.listFiles({
limit: 500, // optional, default: 500
offset: 0 // optional, default: 0
});
Rename Files
await utapi.renameFiles({
key: "2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg",
newName: "myImage.jpg",
});
// Batch rename
await utapi.renameFiles([
{
key: "2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg",
newName: "myImage.jpg",
},
{
key: "1649353b-04ea-48a2-9db7-31de7f562c8d_image2.jpg",
newName: "myOtherImage.jpg",
},
]);
Get Signed URL
const fileKey = "2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg";
const url = await utapi.getSignedURL(fileKey, {
expiresIn: 60 * 60, // 1 hour
// or use time strings:
// expiresIn: '1 hour',
// expiresIn: '3d',
// expiresIn: '7 days',
});
Update ACL
// Make a single file public
await utapi.updateACL(
"2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg",
"public-read"
);
// Make multiple files private
await utapi.updateACL(
[
"2e0fdb64-9957-4262-8e45-f372ba903ac8_image.jpg",
"1649353b-04ea-48a2-9db7-31de7f562c8d_image2.jpg",
],
"private"
);
Accessing Private Files
For files protected by access controls, you'll need to generate short-lived presigned URLs. There are two ways to do this:
Using UTApi:
import { UTApi } from "uploadthing/server";
const utapi = new UTApi();
async function getFileAccess(fileKey: string) {
const signedUrl = await utapi.getSignedUrl(fileKey, {
expiresIn: "1h" // Optional expiration time
});
return signedUrl;
}
Using REST API Endpoint:
async function requestFileAccess(fileKey: string) {
const response = await fetch("/api/requestFileAccess", {
method: "POST",
body: JSON.stringify({ fileKey })
});
const { signedUrl } = await response.json();
return signedUrl;
}
Best Practices
URL Management:
- Always use the CDN URLs provided by UploadThing
- Store file keys rather than full URLs in your database
- Generate presigned URLs on-demand for private files
Security:
- Implement proper access controls in your middleware
- Use short expiration times for presigned URLs
- Validate file access permissions before generating signed URLs
Performance:
- Utilize the CDN for optimal file delivery
- Consider implementing caching for frequently accessed files
- Use appropriate image optimization settings
Example implementation combining these practices:
const fileManager = {
async getFileUrl(fileKey: string, userId: string) {
// Check user permissions
const hasAccess = await checkUserFileAccess(userId, fileKey);
if (!hasAccess) {
throw new Error("Unauthorized access");
}
// Get cached URL if available
const cachedUrl = await cache.get(`file:${fileKey}`);
if (cachedUrl) return cachedUrl;
// Generate new signed URL
const signedUrl = await utapi.getSignedUrl(fileKey, {
expiresIn: "1h"
});
// Cache the URL (for slightly less than expiration time)
await cache.set(`file:${fileKey}`, signedUrl, 50 * 60); // 50 minutes
return signedUrl;
},
async deleteUserFile(fileKey: string, userId: string) {
// Verify ownership
const isOwner = await verifyFileOwnership(userId, fileKey);
if (!isOwner) {
throw new Error("Unauthorized deletion");
}
// Delete file
await utapi.deleteFiles(fileKey);
// Clean up database records
await db.files.delete({
where: { fileKey }
});
// Clear cache
await cache.del(`file:${fileKey}`);
}
};
Conclusion
UploadThing provides a robust, type-safe solution for handling file uploads in Next.js applications. Its key strengths include:
Developer Experience: Type-safe APIs and intuitive integration with React components
Flexibility: Support for both client and server-side uploads with customizable workflows
Security: Built-in file validation, access controls, and secure URL signing
Performance: CDN-backed delivery and resumable uploads for large files
Whether you're building a simple image upload feature or a complex file management system, UploadThing offers the tools and flexibility needed to implement secure and efficient file handling in your applications.
For more information and updates, visit the official UploadThing documentation.
SOCIAL SHARE CARD GENERATOR