Backend Adapters
Fetch / Edge Runtimes

Getting started with Fetch / Edge Runtimes

UploadThing is compatible with any runtime that follow the WinterCG (opens in a new tab).

Common setup

Package Setup

Install the package

npm install uploadthing

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

Set Up A FileRouter

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.

uploadthing.ts
import { createUploadthing, type FileRouter } from "uploadthing/server";
 
const f = createUploadthing();
 
export const uploadRouter = {
  imageUploader: f({
    image: {
      maxFileSize: "4MB",
      maxFileCount: 4,
    },
  }).onUploadComplete((data) => {
    console.log("upload completed", data);
  }),
} satisfies FileRouter;
 
export type OurFileRouter = typeof uploadRouter;

Runtimes-specific setup

See configuration options in server API reference

Astro

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

Read more in our Getting Started with Astro guide.

Elysia

src/index.ts
import { Elysia } from "elysia";
 
import { createRouteHandler } from "uploadthing/server";
 
import { uploadRouter } from "./uploadthing.ts";
 
const { GET, POST } = createRouteHandler({
  router: uploadRouter,
  config: { ... },
});
 
const app = new Elysia().get("/", () => "Hello Elysia");
 
app.group("/api/uploadthing", (app) =>
  app
    .post("/", (context) => POST(context.request))
    .get("/", (context) => GET(context.request)),
);
 
app.listen(3000);

Hono

src/index.ts
import { Hono } from "hono";
 
import { createRouteHandler } from "uploadthing/server";
 
import { uploadRouter } from "./uploadthing.ts";
 
const { GET, POST } = createRouteHandler({
  router: uploadRouter,
  config: { ... },
});
 
const app = new Hono();
 
const ut = new Hono()
  .get("/", (context) => GET(context.req.raw))
  .post("/", (context) => POST(context.req.raw));
 
app.route("/api/uploadthing", ut);
 
export default app;

Cloudflare Workers

src/worker.ts
import { createRouteHandler } from "uploadthing/server";
 
import { uploadRouter } from "./uploadthing.ts";
 
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const handlers = createRouteHandler({
      router: uploadRouter,
      config: {
        /**
         * Since workers doesn't have envs on `process`. We need to pass
         * secret and isDev flag manually.
         */
        uploadthingSecret: env.UPLOADTHING_SECRET,
        isDev: env.ENVIRONMENT === "development",
        /*
         * Cloudflare Workers doesn't support the cache option
         * so we need to remove it from the request init.
         */
        fetch: (url, init) => {
          if (init && "cache" in init) delete init.cache;
          return fetch(url, init);
        },
      },
    });
 
    // World's simplest router. Handle GET/POST requests to /api/uploadthing
    switch (new URL(request.url).pathname) {
      case "/api/uploadthing": {
        if (request.method !== "POST" && request.method !== "GET") {
          return new Response("Method not allowed", { status: 405 });
        }
 
        const response = await handlers[request.method](request);
        if ("cleanup" in response && response.cleanup) {
          /**
           * UploadThing dev server leaves some promises hanging around that we
           * need to wait for to prevent the worker from exiting prematurely.
           */
          ctx.waitUntil(response.cleanup);
        }
        return response;
      }
      default: {
        return new Response("Not found", { status: 404 });
      }
    }
  },
};

Use the FileRouter in your app

Client side usage differs ever so slightly from the fullstack framework setups when using a separate backend server. You'll need to set the URL of your server when you generate the components and helpers.

import { generateUploadButton } from "@uploadthing/react";
 
export const UploadButton = generateUploadButton({
  url: "https://your-server.com/api/uploadthing",
});
// ...

Please note that you might need to setup some CORS rules on your server to allow the client to make requests to the server.

For the remaining usage, please refer to client side examples of the fullstack framework guides: