UploadThing

repository·main·Indexed 11 days ago

https://github.com/pingdotgg/uploadthing

A 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.

Tokens
63.1K
Snippets
204
Records
256
Agent score
88%

What's inside UploadThing

  1. Overview of UploadThing packages and examples

    main

    UploadThing 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.
  2. Supported Backend Adapters for UploadThing

    main

    The 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.

  3. Configure UploadThing via Environment Variables

    main

    In 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 the UPLOADTHING_ 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()
  4. Configure the FileRouter

    main

    A FileRouter defines your upload endpoints. Each FileRoute within the router specifies permitted file types, size limits, and lifecycle callbacks.

    Key components of a FileRoute:

    • Permitted types: e.g., image, video.
    • Constraints: maxFileSize and maxFileCount.
    • 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 as metadata to onUploadComplete.
    • onUploadComplete: A server-side callback that runs after a successful upload. It receives the metadata from the middleware and the file object. Whatever this function returns is sent to the client-side onClientUploadComplete callback.

    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;
  5. Configure and use Access Control Lists (ACL)

    main

    Access 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 the generateSignedURL method on the UTApi. This method accepts an expiration time in seconds as a parameter.

  6. Understand and define File Routes

    main

    File Routes are the endpoints for user uploads, created using the helper from createUploadthing. You define a FileRouter object where each key (slug) represents a specific upload endpoint (e.g., profilePicture, resume). Each route is configured using the f function, 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;
  7. Move skipPolling to server-side configuration

    main

    In v7, the ability to opt-out of waiting for server data (previously skipPolling on the client) has moved to the server-side route configuration using the awaitServerData option. This allows for a faster upload experience by not waiting for the onUploadComplete callback 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 }, 
      ),
    };
  8. Key improvements in UploadThing v7

    main

    UploadThing 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.
  9. Anatomy of UploadButton and UploadDropzone

    main

    To theme the components effectively, you need to understand their internal structure and the elements available for targeting via data-ut-element attributes.

    UploadButton

    Consists of three themeable elements:

    • container: The outermost wrapper.
    • button: Defined using a label element with data-ut-element="button".
    • allowed-content: The element with data-ut-element="allowed-content".

    UploadDropzone

    Consists of five themeable elements:

    • container: The outermost wrapper.
    • upload-icon: The icon element with data-ut-element="upload-icon".
    • label: The label element with data-ut-element="label".
    • button: The button element with data-ut-element="button" (Note: unlike UploadButton, this uses a button tag).
    • allowed-content: The element with data-ut-element="allowed-content".

    All elements support a data-state attribute which can be ready, readying, or uploading.

  10. Protect the UploadThing endpoint from spoofing

    main

    The 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/uploadthing route 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.

  11. Understand Client-Side Uploads

    main

    Client-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:

    1. Define a File Router to specify allowed file types, sizes, and quantities.
    2. Expose the router via a backend adapter (e.g., Express, Fastify, Next.js).
    3. Use built-in SDK components or upload helpers to perform the actual upload.