What Is UploadThing? File Uploads That Just Work in AI-Built Apps

TL;DR

UploadThing is a file upload service that handles the hard parts of letting users upload files in your app. When you ask AI to "add image upload" or "let users attach files," it often generates UploadThing code because it removes the need to configure cloud storage buckets, generate secure upload URLs, or validate file types manually. Think of it as a material delivery service for your construction site — you tell it what materials (file types) you accept, it handles the trucks (upload infrastructure), and your files show up where they need to be. You just need an account, two environment variables, and about 30 lines of code. The most common problems? Missing those environment variables, setting wrong file type restrictions, or hitting size limits you didn't know existed.

Why AI Coders Need to Understand This

Here's a scenario that happens constantly: you're building an app with AI, everything's going great, and then you say something like "now let users upload a profile picture." Suddenly the AI generates a bunch of code you've never seen — file routers, presigned URLs, upload endpoints, middleware functions. The app might even work... until it doesn't.

File upload is one of those things that sounds simple but is actually one of the more complex features in web development. Here's what has to happen behind the scenes every time someone uploads a photo to your app:

  • The file needs somewhere to go. Your web server isn't designed to store user files permanently. You need cloud storage — and configuring that from scratch means setting up AWS S3 buckets, IAM roles, access policies, and CORS headers.
  • The upload needs to be secure. You can't just let anyone upload anything to your storage. You need "presigned URLs" — temporary, one-time-use upload permissions that expire quickly.
  • Files need validation. Someone will try to upload a 500 MB video when you only wanted small profile pictures. Someone else will try to upload an executable file. You need to check file types and sizes before they hit your storage.
  • Your app needs to know when it's done. After the upload completes, your app needs the URL of the uploaded file so it can save it to a database or display it on the page.

Setting all of that up manually is like being your own general contractor, plumber, electrician, and inspector all at once. UploadThing is the subcontractor that handles the entire job. You just tell it what you need.

That's exactly why AI tools reach for it. When Claude, GPT, or Cursor gets asked to add file uploads, they need a solution that handles all of the above in as few lines of code as possible. UploadThing fits that bill. Understanding what it is — and what can go wrong — means you can actually debug the thing when uploads stop working at 11 PM on a Friday.

Real Scenario: "Let Users Upload Profile Pictures"

What you told the AI:

"Add a profile picture upload feature. Users should be able to upload an image up to 4 MB. Store the URL in the user's profile. Use UploadThing."

The AI doesn't just add an <input type="file"> tag. It generates a multi-file setup because UploadThing requires both server-side and client-side code working together. Here's what typically comes back — and we'll break down every piece.

What AI Generated

1. The File Router (Server-Side)

This is the brain of the operation. It defines what users are allowed to upload. Think of it as the gate inspector on a construction site — checking every delivery before it enters.

// src/server/uploadthing.ts
import { createUploadthing, type FileRouter } from "uploadthing/next";

const f = createUploadthing();

export const ourFileRouter = {
  profileImage: f({ image: { maxFileSize: "4MB", maxFileCount: 1 } })
    .middleware(async ({ req }) => {
      // This runs on your server before the upload starts
      const user = await getUserFromRequest(req);
      if (!user) throw new Error("Unauthorized");
      return { userId: user.id };
    })
    .onUploadComplete(async ({ metadata, file }) => {
      // This runs after the upload finishes
      console.log("Upload complete for user:", metadata.userId);
      console.log("File URL:", file.url);
      await updateUserProfile(metadata.userId, file.url);
    }),
} satisfies FileRouter;

export type OurFileRouter = typeof ourFileRouter;

2. The API Route

This connects UploadThing's servers to your app. It's like giving the delivery service your site address so they know where to bring things.

// src/app/api/uploadthing/route.ts
import { createRouteHandler } from "uploadthing/next";
import { ourFileRouter } from "~/server/uploadthing";

export const { GET, POST } = createRouteHandler({
  router: ourFileRouter,
});

3. The Upload Button (Client-Side)

This is what users actually see and click. UploadThing gives you a ready-made button component so you don't have to build one from scratch.

// src/components/ProfileUpload.tsx
"use client";

import { UploadButton } from "~/utils/uploadthing";

export function ProfileUpload() {
  return (
    <UploadButton
      endpoint="profileImage"
      onClientUploadComplete={(res) => {
        // res contains the uploaded file info
        console.log("Files: ", res);
        alert("Upload completed!");
      }}
      onUploadError={(error: Error) => {
        alert(`Upload failed: ${error.message}`);
      }}
    />
  );
}

4. The UploadThing Utils

This tiny file connects your client components to your file router's types. It's boilerplate, but it's needed.

// src/utils/uploadthing.ts
import {
  generateUploadButton,
  generateUploadDropzone,
} from "@uploadthing/react";
import type { OurFileRouter } from "~/server/uploadthing";

export const UploadButton = generateUploadButton<OurFileRouter>();
export const UploadDropzone = generateUploadDropzone<OurFileRouter>();

5. Environment Variables

And here's the part AI sometimes forgets to mention — you need two environment variables in your .env file:

# .env
UPLOADTHING_SECRET=sk_live_xxxxxxxxxxxxxxxxxxxxx
UPLOADTHING_APP_ID=your-app-id-here

Understanding Each Part

Let's walk through what each piece actually does, not how the code syntax works.

The File Router: Your Gate Inspector

Imagine your construction site has a gate with an inspector. Every delivery truck that pulls up gets checked: "What are you carrying? How big is it? Do you have authorization to deliver here?" That's what the file router does.

In the code above, profileImage is an "endpoint" — a named upload slot. You're telling UploadThing: "I have a slot called profileImage, and it only accepts image files, one at a time, max 4 MB." If someone tries to upload a PDF, a video, or a 50 MB image — rejected at the gate.

The middleware function is the authorization check. Before any upload starts, it runs on your server and asks: "Who's trying to upload? Are they logged in? Do they have permission?" If the check fails, the upload never happens. This is input validation — making sure only the right stuff gets through.

The onUploadComplete callback fires after the file is safely stored. This is where you typically save the file's URL to your database so you can display it later. It's like the inspector signing off on the delivery and logging it in the manifest.

The API Route: Your Site Address

For UploadThing's servers to talk to your app, they need to know where your app is. The API route creates two endpoints (GET and POST) at /api/uploadthing that UploadThing uses to coordinate uploads. This is pure plumbing — you set it up once and forget about it.

The Upload Button: What Users See

UploadThing provides pre-built React components — UploadButton and UploadDropzone — so you don't have to build file pickers, progress bars, or drag-and-drop zones from scratch. The endpoint="profileImage" prop tells the button which file router slot to use. When the upload finishes, onClientUploadComplete fires, and when something goes wrong, onUploadError fires.

The Utils File: Connecting the Dots

This file uses TypeScript generics (the angle bracket syntax) to create upload components that know about your specific file router. This means if you try to use endpoint="videoUpload" but you never defined that in your file router, TypeScript will catch the mistake before you even run the code. Think of it as a wiring diagram that makes sure the right outlets connect to the right circuits.

Environment Variables: Your Keys

The UPLOADTHING_SECRET and UPLOADTHING_APP_ID are like your contractor license number and insurance card. They prove to UploadThing's servers that your app is authorized to use the service. Without them, nothing works — and this is the single most common reason uploads fail after AI generates the code. More on that below.

What AI Gets Wrong

AI tools are great at generating the structure of an UploadThing setup, but they consistently stumble on the same set of issues. If your uploads aren't working, start here.

1. Missing Environment Variables

This is the #1 problem. AI generates all the code files perfectly but either forgets to mention the environment variables, uses placeholder values without telling you to replace them, or puts them in the wrong file.

⚠️ What you'll see: The upload button renders, you select a file, and... nothing happens. No error message, no progress bar, just silence. Or you'll see a vague "Something went wrong" error in the console.

✅ Fix: Go to uploadthing.com/dashboard, get your real API keys, and add them to your .env file (or .env.local in Next.js). Then restart your dev server — env vars only load at startup.

2. Wrong File Type Configuration

AI often guesses what file types you want and guesses wrong. You asked for "file uploads" and it only configured image uploads. Or you wanted PDFs and it set up image type only.

UploadThing uses specific type keys:

  • image — JPEG, PNG, GIF, WebP, SVG
  • video — MP4, WebM
  • audio — MP3, WAV, OGG
  • pdf — PDF documents
  • text — Plain text, CSV
  • blob — Any file type (the catch-all)

If you need multiple types, your file router can accept them:

documentUpload: f({
  image: { maxFileSize: "4MB" },
  pdf: { maxFileSize: "8MB" },
})

3. File Size Limits Surprise You

UploadThing's free tier limits uploads to 4 MB per file. AI won't necessarily know what plan you're on, so it might set maxFileSize: "16MB" in the code — which your file router will happily accept, but UploadThing's servers will reject the actual upload if your plan doesn't support it.

The symptom: Small files upload fine. Larger files fail with a confusing error about "file too large" even though your code says it allows bigger files. Your code sets the local limit. UploadThing's servers enforce the plan limit.

4. CORS Errors in Development

CORS (Cross-Origin Resource Sharing) errors show up as red messages in your browser's developer console saying something like "blocked by CORS policy." This happens when your browser tries to talk to UploadThing's servers and gets rejected.

Common cause: AI generated the API route at the wrong path. UploadThing expects it at /api/uploadthing by default. If your route is at /api/upload or /api/files/uploadthing, the client components can't find it. Make sure the route path matches what UploadThing expects, or configure the custom URL in your utils file.

5. Outdated Package Versions

UploadThing has gone through several major version changes. AI might generate code for v5 while your npm install pulled v7. Import paths change between versions (uploadthing/next vs uploadthing/server), function names change, and configuration patterns shift.

The fix: Check what version you installed (npm list uploadthing) and look at the docs for that version. The UploadThing docs site has a version switcher at the top.

6. Forgetting to Install the Package

This sounds obvious, but it happens all the time. AI generates all the code files but doesn't run the install command. You need both packages:

npm install uploadthing @uploadthing/react

If you see errors like Cannot find module 'uploadthing/next' or Module not found: '@uploadthing/react', this is it. Run the install and restart your dev server.

How to Debug UploadThing Issues

When file uploads break, here's a systematic approach to figure out what's wrong. This works whether you're debugging AI-generated code or code you wrote yourself.

Step 1: Check the Basics First

  1. Are the packages installed? Run npm list uploadthing @uploadthing/react. If they show "empty" or "missing," run npm install uploadthing @uploadthing/react.
  2. Are the env vars set? Add console.log(process.env.UPLOADTHING_SECRET) temporarily to your API route. If it prints undefined, your env file isn't being loaded.
  3. Did you restart the dev server? Environment variables only load when the server starts. Every time you change .env, restart with Ctrl+C then npm run dev.

Step 2: Check the Browser Console

Open your browser's developer tools (F12 or right-click → Inspect → Console tab). Try an upload and watch for errors. Common messages and what they mean:

  • "Failed to fetch" — The API route doesn't exist at the expected path. Check that your route file is at the right location.
  • "UPLOADTHING_SECRET is required" — Env var is missing or misspelled.
  • "Invalid file type" — The file you're uploading doesn't match what the file router accepts. Check the type keys in your file router.
  • "File too large" — Either your code's maxFileSize or UploadThing's plan limit is smaller than the file.
  • "Unauthorized" — Your middleware function is rejecting the upload. Check that the user is logged in.

Step 3: Check the Network Tab

In dev tools, switch to the Network tab. Try the upload again and look for requests to /api/uploadthing. Click on them and check:

  • Status 404: Route doesn't exist. Wrong file path.
  • Status 401/403: Auth problem. Check env vars and middleware.
  • Status 500: Server error. Check your terminal where the dev server is running for error messages.

Step 4: Ask AI with Context

If you're still stuck, give your AI the error message, the relevant code file, and your UploadThing version. A prompt like this works well:

Debug prompt for your AI:

"My UploadThing upload fails with [exact error]. I'm using uploadthing version [X]. Here's my file router: [paste code]. Here's my .env: [paste variable names, not values]. What's wrong?"

How UploadThing Actually Works (The Simple Version)

You don't need to understand all the plumbing, but having a mental model helps when things break. Here's the flow in construction terms:

  1. User picks a file — like a project manager filling out a material order form.
  2. Your app calls UploadThing's API — "Hey, I've got a delivery coming. It's a JPEG image, 2 MB. Here are my credentials."
  3. UploadThing checks the order — Are the credentials valid? Is this file type allowed? Is the size within limits? Does the middleware say OK?
  4. UploadThing creates a "presigned URL" — This is a one-time-use delivery address that expires quickly. It's like a temporary gate pass for one specific delivery truck.
  5. The file uploads directly to cloud storage — The file goes straight from the user's browser to UploadThing's storage. It doesn't pass through your server, which keeps things fast.
  6. UploadThing notifies your app — "Delivery complete. Here's the permanent URL where the file lives." Your onUploadComplete callback fires.
  7. You save the URL — Store it in your database, display it on the page, whatever you need.

The key insight: files don't go through your server. They go directly from the user's browser to UploadThing's cloud storage. Your server just coordinates the handshake. This is why it's fast and why your server doesn't get bogged down handling large file transfers.

When UploadThing Might Not Be the Right Fit

UploadThing is great for most vibe coding projects, but it's not the only option. Here's when you might want something different:

  • You need to stay completely free: The free tier has storage and size limits. If your app handles lots of large files, costs will add up.
  • You need full control over storage: UploadThing manages the storage for you. If you need files in your own AWS S3 bucket for compliance or data sovereignty reasons, you'd configure S3 directly.
  • You're not using React or Next.js: While UploadThing supports other frameworks now, the experience is smoothest with React-based apps. For a plain HTML site or a Python backend, other solutions might fit better.
  • You just need simple form uploads: If you're building a basic contact form with one file attachment, a full UploadThing setup might be overkill. A simple form submission to your server could work fine.

For most AI-built apps — especially Next.js projects — UploadThing is the right call. The AI picked it for a reason.

What to Learn Next

Now that you understand what UploadThing does and how to troubleshoot it, here are the connected concepts that will make you more effective:

  • Environment Variables — They're not just for UploadThing. Every API service your app talks to uses them. Understanding how .env files work will save you hours of debugging across every project.
  • APIs — UploadThing is an API service. Understanding the request-response pattern will help you debug not just file uploads but any external service your AI integrates.
  • Input Validation — The file router's middleware is one example of input validation. Learn how this concept applies everywhere — form fields, API requests, database queries.
  • npm — UploadThing gets installed via npm. If you're still fuzzy on how package management works, that guide covers it all.
  • How to Debug AI-Generated Code — The debugging steps above are just one application of a general approach. That guide covers the full methodology.

Frequently Asked Questions

Is UploadThing free to use?

UploadThing offers a free tier that includes 2 GB of storage and basic upload functionality. For most hobby projects and prototypes, the free tier is enough. Paid plans add more storage, higher file size limits, and team features. You can start building without entering a credit card.

Does UploadThing only work with Next.js?

UploadThing was originally built for Next.js, but it now supports other frameworks including SolidStart, Nuxt, Astro, and Express. That said, the Next.js integration is the most mature and the one AI tools generate most often. If your AI built a Next.js app, UploadThing will work out of the box.

Where do uploaded files actually go?

UploadThing stores your files in cloud storage managed by their service. You get back a URL for each uploaded file that you can use in your app — to display images, link to documents, or serve downloads. You don't need to set up your own storage buckets or configure AWS S3.

What is the maximum file size UploadThing supports?

The maximum file size depends on your plan. The free tier supports files up to 4 MB per upload. Paid plans increase this to 512 MB or more. You can also set custom size limits per file type in your file router — for example, allowing 4 MB images but only 1 MB PDFs.

Why does my UploadThing upload fail with no error message?

Silent upload failures are almost always caused by missing or incorrect environment variables. UploadThing needs UPLOADTHING_SECRET and UPLOADTHING_APP_ID in your .env file. If these are missing, the upload request never authenticates and fails silently. Check your .env file, make sure the variable names are exact, and restart your dev server after adding them.