Uploadthing
Uploadthing as a MainSDK files driver, with setup, main.yaml, a direct browser upload example and the guarantees MainSDK keeps on top.
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.
filesBuilding for 0.3Driver uploadthing UploadthingFrom the files-share spec
files:
driver: uploadthingSwap to another provider
Before you start
- Create an Uploadthing app and copy its token.
- Store it in your secrets manager as
UPLOADTHING_TOKEN. - Decide the largest file and the types you accept; MainSDK enforces both on every upload URL.
Configure
files:
driver: uploadthing
key: secret:UPLOADTHING_TOKEN
scan: true
max-size: 16MBUse it
Issue an upload on the server, upload from the browser, then complete.
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);
}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. Uses3for 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 checkfails if route code importsuploadthingdirectly;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
| Code | Cause | Fix |
|---|---|---|
MAIN-FILES-0002 | files.url() was called for a reader access denies (403). | Read as the owner, or add an access rule for the reader. |
MAIN-FILES-0005 | The 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-0010 | mainsdk check found uploadthing imported in route code. | Use files.uploadUrl() and files.complete(), and keep the store as the files driver. |
MAIN-FILES-0016 | mainsdk 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-0004 | key is a literal value, not a secret: reference. | Move the value to your secrets manager and write secret:UPLOADTHING_TOKEN. |
For agents
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?