Getting Started

Expo Setup

UploadThing is the easiest way to add file uploads to your native mobile applications powered by export.

Check out a full example here (opens in a new tab)

Package Setup

Install the packages

npm install uploadthing @uploadthing/expo expo-image-picker expo-document-picker

Add env variables


If you don't already have a uploadthing secret key, sign up (opens in a new tab) and create one from the dashboard! (opens in a new tab)

UPLOADTHING_SECRET=... # A secret key for your app (starts with sk_live_)
UPLOADTHING_APP_ID=... # Your app id
EXPO_PUBLIC_SERVER_URL=... # Absolute URL to your server

Set Up A FileRouter

Steps 1 and 2 below assumes you're using Expo API routes (opens in a new tab) which are currently experimental. You can also choose to use a standalone server using some of our backend adapters, e.g. Fetch

Creating your first FileRoute


For more details on how to create a file router, see the router docs

All files uploaded to uploadthing are associated with a FileRoute. The following is a very minimalistic example, with a single FileRoute "imageUploader". Think of a FileRoute similar to an endpoint, it has:

  • Permitted types ["image", "video", etc]
  • Max file size
  • (Optional) middleware to authenticate and tag requests
  • onUploadComplete callback for when uploads are completed

To get full insight into what you can do with the FileRoutes, please refer to the File Router API.

import { createUploadthing, UploadThingError } from "uploadthing/server";
import type { FileRouter } from "uploadthing/server";
const f = createUploadthing();
const auth = (req: Request) => ({ id: "fakeId" }); // Fake auth function
const uploadRouter = {
  profileImage: f({
    image: {
      maxFileSize: "4MB",
    .middleware(async ({ req }) => {
      // This code runs on your server before upload
      const user = await auth(req);
      // 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: };
    .onUploadComplete(({ file, metadata }) => {
      // This code RUNS ON YOUR SERVER after upload
      console.log("Upload complete for userId:", metadata.userId);
      console.log("file url", file.url);
      // !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback
      return { uploadedBy: metadata.userId };
} satisfies FileRouter;
export type UploadRouter = typeof uploadRouter;

Create an API route using the FileRouter

import { createRouteHandler } from "uploadthing/server";
const uploadRouter = { ... } satisfies FileRouter;
export type UploadRouter = typeof uploadRouter;
export const { GET, POST } = createRouteHandler({
  router: uploadRouter,
  // Apply an (optional) custom config:
  // config: { ... },

See configuration options in server API reference

Generate typed hooks

Unlike the other UploadThing packages, the Expo package does not include any prebuilt components. Instead, we provide some helper hooks to help interact with the native file pickers.

import { generateReactNativeHelpers } from "@uploadthing/expo";
import type { UploadRouter } from "~/app/api/uploadthing+api";
export const { useImageUploader, useDocumentUploader } =
     * Your server url.
     * @default process.env.EXPO_PUBLIC_SERVER_URL
     * @remarks In dev we will also try to use Expo.debuggerHost
    url: "",

Use the FileRouter in your app

import { openSettings } from "expo-linking";
import { Alert, Pressable, StyleSheet, Text, View } from "react-native";
import { useImageUploader } from "~/utils/uploadthing";
export default function Home() {
  const { openImagePicker, isUploading } = useImageUploader({
     * Any props here are forwarded to the underlying `useUploadThing` hook.
     * Refer to the React API reference for more info.
    onClientUploadComplete: () => Alert.alert("Upload Completed"),
    onUploadError: (error) => Alert.alert("Upload Error", error.message),
  return (
        onPress={() => {
            input, // Matches the input schema from the FileRouter endpoint
            source: "library", // or "camera"
            onInsufficientPermissions: () => {
                "No Permissions",
                "You need to grant permission to your Photos to use this",
                  { text: "Dismiss" },
                  { text: "Open Settings", onPress: openSettings },
        <Text>Select Image</Text>
const styles = StyleSheet.create({
  button: { ... },


Follow the Expo Router API Routes Deployment Guide (opens in a new tab)