UploadThing
repository·main·Indexed 11 days ago
https://github.com/pingdotgg/uploadthingA file uploading solution providing framework-specific SDKs for React and Solid, as well as a framework-agnostic core. It supports various backend adapters including Elysia, Express, Fastify, Hono, and H3, and integrates with frameworks like Next.js (App and Pages Router), Nuxt, SvelteKit, SolidStart, and Remix.
What's inside UploadThing
- The UploadThing Raycast Extension allows you to upload files directly to your UploadThing application using Raycast. This provides a streamlined workflow for file management within your development environment.
Overview of UploadThing packages and examples
mainUploadThing is a tool for uploading files. The repository provides several packages and framework-specific examples to help you integrate file uploading into your applications:
Core Packages
@uploadthing/react: Components and hooks for React projects.@uploadthing/solid: Components and hooks for Solid projects.uploadthing: Framework-agnostic server and client logic.
Framework Examples
- Next.js App Directory: Minimal example using the Next.js App Router.
- Next.js Pages Directory: Minimal example using the Next.js Pages Router.
- SolidStart SSR: Minimal example using Server-Side Rendering with SolidStart.
- Other examples: Includes Nuxt, SvelteKit, Expo, and backend adapter implementations.
Supported Backend Adapters for UploadThing
mainThe backend adapter pattern allows UploadThing to integrate with various web frameworks. This example repository demonstrates implementations for the following servers located in the
server/src/directory:- Elysia (
elysia.ts) - Express (
express.ts) - Fastify (
fastify.ts) - Hono (
hono.ts) - H3 (
h3.ts)
For detailed implementation guides, refer to the official documentation at https://docs.uploadthing.com/backend-adapters.
- Elysia (
Configure UploadThing via Environment Variables
mainIn v7, you can use a new configuration provider. Most options passed to configuration objects (like
createRouteHandler#config) can now be set via environment variables using theUPLOADTHING_prefix in constant case.Precedence: If both an environment variable and an options object are provided, the options object takes precedence.
// Instead of: const api = new UTApi({ logLevel: 'Info', }) // You can use: // process.env.UPLOADTHING_LOG_LEVEL = 'Info' const api = new UTApi()Configure the FileRouter
mainA
FileRouterdefines your upload endpoints. EachFileRoutewithin the router specifies permitted file types, size limits, and lifecycle callbacks.Key components of a
FileRoute:- Permitted types: e.g.,
image,video. - Constraints:
maxFileSizeandmaxFileCount. middleware: A server-side function that runs before the upload. It can be used for authentication. If it throws an error (e.g.,UploadThingError), the upload is blocked. Whatever it returns is passed asmetadatatoonUploadComplete.onUploadComplete: A server-side callback that runs after a successful upload. It receives themetadatafrom the middleware and thefileobject. Whatever this function returns is sent to the client-sideonClientUploadCompletecallback.
Example implementation in
app/api/uploadthing/core.ts:import { createUploadthing, type FileRouter } from "uploadthing/next"; import { UploadThingError } from "uploadthing/server"; const f = createUploadthing(); export const ourFileRouter = { imageUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 1, }, }) .middleware(async ({ req }) => { // Perform auth logic here return { userId: "user_123" }; }) .onUploadComplete(async ({ metadata, file }) => { console.log("Upload complete for:", metadata.userId); return { uploadedBy: metadata.userId }; }), } satisfies FileRouter; export type OurFileRouter = typeof ourFileRouter;import { createUploadthing, type FileRouter } from "uploadthing/next"; import { UploadThingError } from "uploadthing/server"; const f = createUploadthing(); // FileRouter for your app, can contain multiple FileRoutes export const ourFileRouter = { // Define as many FileRoutes as you like, each with a unique routeSlug imageUploader: f({ image: { /** * For full list of options and defaults, see the File Route API reference * @see https://docs.uploadthing.com/file-routes#route-config */ maxFileSize: "4MB", maxFileCount: 1, }, }) // Set permissions and file types for this FileRoute .middleware(async ({ req }) => { // This code runs on your server before upload const user = { id: "fakeId" }; // Example auth // If you throw, the user will not be able to upload if (!user) throw new UploadThingError("Unauthorized"); // Whatever is returned here is accessible in onUploadComplete as `metadata` return { userId: user.id }; }) .onUploadComplete(async ({ metadata, file }) => { // This code RUNS ON YOUR SERVER after upload console.log("Upload complete for userId:", metadata.userId); console.log("file url", file.ufsUrl); // !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback return { uploadedBy: metadata.userId }; }), } satisfies FileRouter; export type OurFileRouter = typeof ourFileRouter;- Permitted types: e.g.,
Configure and use Access Control Lists (ACL)
mainAccess Control Lists (ACL) allow you to restrict file access. By default, files are accessible via their URL (
<APP_ID>.ufs.sh/f/<FILE_KEY>).Supported ACL types:
public-read: Files are accessible via their public URL.private: Files are not accessible via their URL. Access requires a short-lived signed URL.
Configuration:
- Set the default ACL in your UploadThing dashboard under
Regions and ACL. - You can toggle whether to allow overriding the default ACL on a per-request basis.
Accessing Private Files: To access files set to
private, use thegenerateSignedURLmethod on theUTApi. This method accepts an expiration time in seconds as a parameter.Understand and define File Routes
mainFile Routes are the endpoints for user uploads, created using the helper from
createUploadthing. You define aFileRouterobject where each key (slug) represents a specific upload endpoint (e.g.,profilePicture,resume). Each route is configured using theffunction, which returns a builder object for chaining lifecycle methods like.middleware(),.onUploadComplete(), and.input().import { createUploadthing, type FileRouter } from "uploadthing/server"; const f = createUploadthing(); export const uploadRouter = { profilePicture: f(["image"]) .middleware(({ req }) => auth(req)) .onUploadComplete((data) => console.log("file", data)), } satisfies FileRouter; export type UploadRouter = typeof uploadRouter;Move skipPolling to server-side configuration
mainIn v7, the ability to opt-out of waiting for server data (previously
skipPollingon the client) has moved to the server-side route configuration using theawaitServerDataoption. This allows for a faster upload experience by not waiting for theonUploadCompletecallback to finish before triggering the client-side callback.// On the server import { createUploadThing } from "uploadthing/server"; const f = createUploadThing(); export const uploadRouter = { myUploader: f( { image: { maxFileSize: "16MB" } }, // Opt-out of waiting for server data { awaitServerData: false }, ), };Key improvements in UploadThing v7
mainUploadThing v7 features a major infrastructure overhaul that moves away from direct S3 uploads to a dedicated Ingest Server model. Key benefits include:
- Increased Speed: Benchmarks show up to 509% faster single-image uploads.
- Resumable Uploads: Uploads can be paused and resumed seamlessly, which is critical for users with unstable internet connections.
- Reduced Latency: The number of network hops has been cut in half by removing the need for client-side polling and reducing the number of API interactions.
- Smaller Bundle Size: Client-side JavaScript bundle size has been reduced by over 30%.
- Improved Reliability: The new flow removes the need for event listeners to notify your server of completion; instead, the Ingest Server notifies your server and sends the response directly to the browser.
Anatomy of UploadButton and UploadDropzone
mainTo theme the components effectively, you need to understand their internal structure and the elements available for targeting via
data-ut-elementattributes.UploadButton
Consists of three themeable elements:
container: The outermost wrapper.button: Defined using alabelelement withdata-ut-element="button".allowed-content: The element withdata-ut-element="allowed-content".
UploadDropzone
Consists of five themeable elements:
container: The outermost wrapper.upload-icon: The icon element withdata-ut-element="upload-icon".label: The label element withdata-ut-element="label".button: The button element withdata-ut-element="button"(Note: unlikeUploadButton, this uses abuttontag).allowed-content: The element withdata-ut-element="allowed-content".
All elements support a
data-stateattribute which can beready,readying, oruploading.Protect the UploadThing endpoint from spoofing
mainThe UploadThing callback request functions like a webhook, triggered when a file upload to the storage provider is complete. To prevent spoofing, the callback data is signed using HMAC SHA256 with your API key.
Important: Since SDK version
v6.7, this signature is automatically verified by the UploadThing SDK. As long as you are using version^6.7, no additional manual verification logic is required to protect your endpoint from spoofing.CRITICAL WARNING: Do not protect the entire
/api/uploadthingroute with authentication middleware (e.g., at the Next.js middleware level). This endpoint must remain publicly accessible because it is called as a webhook by UploadThing's servers.Understand Client-Side Uploads
mainClient-side uploads are the most efficient way to handle file transfers. Instead of routing binary data through your server (which incurs ingress/egress fees), your server generates presigned URLs. The client then uses these URLs to upload files directly to UploadThing.
To implement this, you typically:
- Define a File Router to specify allowed file types, sizes, and quantities.
- Expose the router via a backend adapter (e.g., Express, Fastify, Next.js).
- Use built-in SDK components or upload helpers to perform the actual upload.