MainSDK
Search⌘K
Integrations

Uploadthing

Uploadthing as a MainSDK files driver, with setup, main.yaml, a direct browser upload example and the guarantees MainSDK keeps on top.

1 interface1 driverWritten guide
Updated 2026-10-28

Uploadthing is a hosted file upload service for TypeScript apps. The uploadthing driver for files lets you keep Uploadthing's storage and delivery while MainSDK adds owner scope, scanning and short-lived URLs. You would pick it for the quickest path to uploads in a Next.js app without running a bucket.

Interfaces it serves

Pick Uploadthing in main.yaml for any of these. Your calls stay the same, and you can swap later.

Before you start

  1. Create an Uploadthing app and copy its token.
  2. Store it in your secrets manager as UPLOADTHING_TOKEN.
  3. Decide the largest file and the types you accept; MainSDK enforces both on every upload URL.

Configure

main.yaml
files:
  driver: uploadthing
  key: secret:UPLOADTHING_TOKEN
  scan: true
  max-size: 16MB

Use it

Issue an upload on the server, upload from the browser, then complete.

app/api/uploads/route.ts
import { auth, files } from "mainsdk";

export async function POST(req: Request) {
  const session = await auth.require(req);
  const upload = await files.uploadUrl({
    owner: session.user,
    maxSize: "8MB",
    types: ["image/png", "image/jpeg", "image/webp"],
  });
  return Response.json(upload);
}
app/api/uploads/[id]/complete/route.ts
import { auth, files } from "mainsdk";

export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
  const session = await auth.require(req);
  const { id } = await params;                           // upload.ref.id
  const ref = await files.complete(id, { owner: session.user }); // only the uploader can finish it
  const url = await files.url(ref, { for: session.user, expiresIn: "1h" });
  return Response.json({ url });
}

The browser sends the file to upload.url with upload.method. The browser function, the Hono version and the offline test are in Upload files with S3, Uploadthing or Vercel Blob.

Every file is stored private under your file id as its customId, and read through a signed URL that expires. The driver cannot meet two guarantees, and mainsdk check reports each as a MAIN-FILES-0016 warning:

  • upload-single-use: an Uploadthing presigned upload URL can be used more than once until it expires (MainSDK's own targets last 15 minutes).
  • data-region: Uploadthing chooses where a file lives, so it cannot be kept in the tenant region. Use s3 for EU and India tenants.

An interrupted upload is retried with the same idempotencyKey; Uploadthing's ingest takes the file in one request, so a retry sends it again and one file exists.

Local development and testing

Use the local driver in development and createTestApp() from @mainsdk/testing in tests. Neither needs an Uploadthing account.

What MainSDK adds on top

  • Owner-scoped reads and short-lived URLs on every file.
  • A scan before a file is readable, and idempotent uploads.
  • mainsdk check fails if route code imports uploadthing directly; files.raw() gives you its client and is listed as outside the guarantees.

Verify

Run npx mainsdk check --env prod. A pass exits 0 and names no MAIN-FILES or MAIN-CONFIG finding.

If it fails

CodeCauseFix
MAIN-FILES-0002files.url() was called for a reader access denies (403).Read as the owner, or add an access rule for the reader.
MAIN-FILES-0005The scan in files.complete() found an infected file. The upload fails and the file is quarantined.Ask the user for a clean file. Nothing to fix in code.
MAIN-FILES-0010mainsdk check found uploadthing imported in route code.Use files.uploadUrl() and files.complete(), and keep the store as the files driver.
MAIN-FILES-0016mainsdk check named a guarantee Uploadthing cannot meet (a warning).Read the gap in the message; use s3 or getqr if you need it.
MAIN-CONFIG-0004key is a literal value, not a secret: reference.Move the value to your secrets manager and write secret:UPLOADTHING_TOKEN.

For agents

text
Goal: use uploadthing as the files driver and add owner-scoped uploads to this app.
Read: docs/specs/files-share.md, this page, main.yaml.
Touch only: main.yaml, main.env.dev.yaml, app/api/uploads/**, one test file.
Do: set files.driver to uploadthing with key: secret:UPLOADTHING_TOKEN;
issue uploads with files.uploadUrl({ owner, maxSize, types });
send the file to upload.url with upload.method; call files.complete(upload.ref.id, { owner }) as the same user, then files.url(ref, { for, expiresIn }).
Verify: npx mainsdk check --json --agent
Never weaken, skip or loosen a check to make it pass.
Never import uploadthing in route code.

Swap it

Change driver to s3, vercel-blob or getqr. Your calls stay the same. See Swap a driver.

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet