createUploadthing
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.
Defaults
All routes default to a max file count of 1. The file size defaults are below
File Type | Default Max Size |
---|---|
image | 4MB |
video | 16MB |
audio | 8MB |
blob | 8MB |
4MB | |
text | 64kB |
MimeType | 4MB |
input
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";
f(["image"])
.input(z.object({ foo: z.string() }))
.middleware(async ({ req, input }) => {
input;
// ^? { foo: string }
return {};
})
.onUploadComplete(async () => {});
middleware
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";
f(["image"])
.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";
f(["image"])
.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
});
onUploadComplete
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.
createRouteHandler
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 tov6.3
.
import { createRouteHandler } from "uploadthing/next";
import { uploadRouter } from "~/server/uploadthing.ts";
export const { GET, POST } = createRouteHandler({
router: uploadRouter,
config: { ... },
});
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;
};