API Reference


The helper function to create an UploadThing instance. MAKE SURE YOU IMPORT IT FROM THE RIGHT PLACE. The export name ensures your file routes' middleware functions are typed correctly.

import { createUploadthing, type FileRouter } from "uploadthing/next";
const f = createUploadthing();
export const uploadRouter = { ... };
// ...
f({ ... })
  .middleware(({ req }) => {
    //           ^? req: NextRequest

File Routes

File Routes are the routes you create with the helper instantiated by createUploadthing. Think of them as the "endpoints" for what your users can upload. The keys in the object are the names of your endpoints.

import { createUploadthing, type FileRouter } from "uploadthing/next";
const f = createUploadthing();
export const ourFileRouter = {
  // 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)),
} satisfies FileRouter;
export type OurFileRouter = typeof ourFileRouter;

Route Config

// Your file types can be any of the following types
type MimeType = /** any web mime-type */
type ValidFileTypes = "image" | "video" | "audio" | "blob" | "pdf" | "text" | MimeType;
type ContentDisposition = "inline" | "attachment";
type ACL = "public-read" | "private";
// The input to your f() function can be an array of file types OR an object of them w/ config
type FileRouterInput =
  | ValidFileTypes[]
  | {
      [key: ValidFileTypes]: {
        maxFileSize?: string;
        maxFileCount?: number;
        minFileCount?: number;
        contentDisposition?: ContentDisposition = "inline";
        acl?: ACL;

MIME types can also be used as keys in the FileRouter. For example: use application/json to only allow JSON files to be uploaded. You can read more about MIME types on MDN ↗ (opens in a new tab).

The contentDisposition option can be used to override the default inline disposition set at the storage provider. This is useful if you want your files downloaded instead of previewed in the browser. You can read more about content disposition on MDN ↗ (opens in a new tab).


ACL can only be overridden if you have enabled the "Allow Overriding ACL" setting on the UploadThing dashboard. Attempts to override the ACL without enabling this setting will result in an error.


All routes default to a max file count of 1. The file size defaults are below

File TypeDefault Max Size


You can pass a zod schema to validate user input from the client. This data comes from the client when the upload starts. If validation here fails, an error will be thrown and none of your middleware n'or onUploadComplete functions will be executed.

The input is validated on your server and only leaves your server if you pass it along from the .middleware to the .onUploadComplete. If you only use the input in the middleware without returning it, the Uploadthing server won't have any knowledge of it.

If you want to disable FE components based on when your input is not satisfied, you can place your validator in a shared file, so that you can import it in both the server-side .input() and on the client-side for your disabled prop logic.

import { z } from "zod";
  .input(z.object({ foo: z.string() }))
  .middleware(async ({ req, input }) => {
    // ^? { foo: string }
    return {};
  .onUploadComplete(async () => {});


This is the function where you authorize a user to do an upload. You can also tag the upload with metadata here. Example using Clerk:

import { currentUser } from "@clerk/nextjs";
import { UploadThingError } from "uploadthing/server";
  .middleware(async ({ req, res }) => {
    const user = await currentUser();
    // Throw if user isn't signed in
    if (!user)
      throw new UploadThingError(
        "You must be logged in to upload a profile picture",
    // Return userId to be used in onUploadComplete
    return { userId: user.id };
  .onUploadComplete(async ({ metadata }) => {
    console.log("Uploaded by user", metadata.userId);

Note: By default, a thrown UploadThingError's message will be sent to the client's onError. All other errors are turned into generic failure messages to avoid leaking sensitive information.

As of v6.4, you can also tag your metadata using the UTFiles symbol to override the uploaded files attributes. This can be used to either rename the file, or set a custom identifer for the file:

import { UTFiles } from "uploadthing/server";
  .middleware(async ({ req, files }) => {
    const fileOverrides = files.map((file) => {
      const newName = sluggify(file.name);
      const myIdentifier = generateId();
      return { ...file, name: newName, customId: myIdentifier };
    // Return userId to be used in onUploadComplete
    return { foo: "bar" as const, [UTFiles]: fileOverrides };
  .onUploadComplete(async ({ metadata, file }) => {
    // The UTFIles symbol is stripped from the metadata
    metadata; // { foo: "bar" }
    file.customId; // myIdentifier


This is the function you use to do something with the uploaded file, such as persisting it to your database. Whatever you returned in the middleware will be accessible here.

As of v6.0, you can return JSON serializable data from this function, which will be passed to the clientside onClientUploadComplete callback.


All adapters exports a createRouteHandler function that exposes your router to the world. By default, you should only have to pass your router to this function, although there are some extra configuration options available.

The names of the exported createRouteHandler is different prior to v6.3.

import { createRouteHandler } from "uploadthing/next";
import { uploadRouter } from "~/server/uploadthing.ts";
export const { GET, POST } = createRouteHandler({
  router: uploadRouter,
  config: { ... },


type RouteHandlerConfig = {
   * The URL to where your route handler is hosted. This is called via webhook
   * after your file is uploaded. UploadThing attempts to automatically detect
   * this value based on the request URL and headers. You can override this if
   * the automatic detection fails.
  callbackUrl?: string;
   * Your UploadThing app id. You can find this on the UploadThing dashboard.
   * @default `env.UPLOADTHING_APP_ID`
  uploadthingId?: string;
   * Your UploadThing API key. You can find this on the UploadThing dashboard.
   * @default `env.UPLOADTHING_KEY`
  uploadthingSecret?: string;
   * Enable more verbose logging.
   * @default `info`
   * @since v6.2
  logLevel?: "error" | "warn" | "info" | "debug" | "trace";
   * Used to determine whether to run dev hook or not
   * @default `env.NODE_ENV === "development" || env.NODE_ENV === "dev"`
   * @since v6.3
  isDev?: boolean;
   * Used to override the fetch implementation
   * @default `globalThis.fetch`
   * @since v6.3
  fetch?: FetchEsque;