Expo Setup

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

Setting up your environment

Install the packages

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

Add env variables

UPLOADTHING_TOKEN=... # A token for interacting with the SDK
EXPO_PUBLIC_SERVER_URL=... # Absolute URL to your server

Set Up A FileRouter

Creating your first FileRoute

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
  • How many files are allowed to be uploaded
  • (Optional) input validation to validate client-side data sent to the route
  • (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.

src/app/api/uploadthing+api.ts

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 = {
  // 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,
    },
  })
    .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: user.id };
    })
    .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

src/app/api/uploadthing+api.ts

import { createRouteHandler } from "uploadthing/server";

const uploadRouter = { ... } satisfies FileRouter;
export type UploadRouter = typeof uploadRouter;

const handlers = createRouteHandler({
  router: uploadRouter,

  // Apply an (optional) custom config:
  // config: { ... },
})
export { handlers as GET, handlers as POST }

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.

src/utils/uploadthing.ts

import { generateReactNativeHelpers } from "@uploadthing/expo";

import type { UploadRouter } from "~/app/api/uploadthing+api";

export const { useImageUploader, useDocumentUploader } =
  generateReactNativeHelpers<UploadRouter>({
    /**
     * Your server url.
     * @default process.env.EXPO_PUBLIC_SERVER_URL
     * @remarks In dev we will also try to use Expo.debuggerHost
     */
    url: "https://my-server.com",
  });

Use the FileRouter in your app

src/routes/index.tsx

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("imageUploader", {
    /**
     * 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 (
    <View>
      <Pressable
        style={styles.button}
        onPress={() => {
          openImagePicker({
            input, // Matches the input schema from the FileRouter endpoint
            source: "library", // or "camera"
            onInsufficientPermissions: () => {
              Alert.alert(
                "No Permissions",
                "You need to grant permission to your Photos to use this",
                [
                  { text: "Dismiss" },
                  { text: "Open Settings", onPress: openSettings },
                ],
              );
            },
          })
        }}
      >
        <Text>Select Image</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  button: { ... },
});

Deployment

Follow the Expo Router API Routes Deployment Guide

Was this page helpful?