Migration from UploadThing
Step-by-step migration guide from UploadThing to UploadKit — API equivalents, prop renames, and new features.
UploadKit is a direct UploadThing alternative built on the same presigned URL approach. Most apps migrate in under an hour. This guide maps every UploadThing concept to its UploadKit equivalent.
API Equivalents
| UploadThing | UploadKit | Notes |
|---|---|---|
createUploadthing() | createUploadKitHandler() | Handler factory function |
f() chain | satisfies FileRouter object | Object syntax instead of chain |
useUploadThing() | useUploadKit() | Same hook API, different name |
<UploadButton endpoint="..."> | <UploadButton route="..."> | endpoint → route prop |
<UploadDropzone endpoint="..."> | <UploadDropzone route="..."> | endpoint → route prop |
UTApi | createUploadKit() (core SDK) | Server-side file operations |
.middleware() | middleware: (ctx) => {} | Inline function in route config |
.onUploadComplete() | onUploadComplete: (args) => {} | Inline function in route config |
@uploadthing/next | @uploadkitdev/next | Package rename |
@uploadthing/react | @uploadkitdev/react | Package rename |
Key Differences
1. File router syntax
UploadThing uses a builder chain:
// UploadThing
const f = createUploadthing();
const ourFileRouter = {
imageUploader: f({ image: { maxFileSize: '4MB' } })
.middleware(async ({ req }) => {
const user = await auth(req);
return { userId: user.id };
})
.onUploadComplete(async ({ metadata, file }) => {
console.log('file url', file.url);
}),
};UploadKit uses a plain object with satisfies:
// UploadKit
const router = {
imageUploader: {
maxFileSize: '4MB',
allowedTypes: ['image/*'],
middleware: async ({ req }) => {
const user = await auth(req);
return { userId: user.id };
},
onUploadComplete: async ({ file, metadata }) => {
console.log('file url', file.url);
},
},
} satisfies FileRouter;2. endpoint vs route prop
// UploadThing
<UploadButton endpoint="imageUploader" />
// UploadKit
<UploadButton route="imageUploader" />3. BYOS mode
UploadThing does not support bring-your-own-storage. UploadKit lets you use your own S3 or R2 bucket with zero changes to your frontend code:
// UploadKit only — server-side BYOS config
export const { GET, POST } = createUploadKitHandler({
router,
storage: {
provider: 'r2',
accountId: process.env.CF_ACCOUNT_ID!,
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
bucket: process.env.R2_BUCKET_NAME!,
publicUrl: process.env.R2_PUBLIC_URL!,
},
});4. CSS theming
UploadThing requires per-component class overrides. UploadKit supports CSS custom properties for global theming — one declaration in globals.css styles all components:
/* UploadKit — global theme override */
:root {
--uk-accent: #6366f1;
--uk-radius: 8px;
}Step-by-step migration
Step 1: Replace packages
# Remove UploadThing packages
pnpm remove uploadthing @uploadthing/next @uploadthing/react
# Install UploadKit packages
pnpm add @uploadkitdev/next @uploadkitdev/reactStep 2: Update the file router
import { createUploadthing, type FileRouter } from 'uploadthing/next';
const f = createUploadthing();
export const ourFileRouter = {
imageUploader: f({ image: { maxFileSize: '4MB' } })
.middleware(async ({ req }) => {
const user = await auth(req);
if (!user) throw new Error('Unauthorized');
return { userId: user.id };
})
.onUploadComplete(async ({ metadata, file }) => {
await db.files.create({ url: file.url, userId: metadata.userId });
}),
} satisfies FileRouter;
export type OurFileRouter = typeof ourFileRouter;import { createUploadKitHandler } from '@uploadkitdev/next';
import type { FileRouter } from '@uploadkitdev/next';
const router = {
imageUploader: {
maxFileSize: '4MB',
allowedTypes: ['image/*'],
middleware: async ({ req }) => {
const user = await auth(req);
if (!user) throw new Error('Unauthorized');
return { userId: user.id };
},
onUploadComplete: async ({ file, metadata }) => {
await db.files.create({ url: file.url, userId: metadata.userId });
},
},
} satisfies FileRouter;
export const { GET, POST } = createUploadKitHandler({ router });Step 3: Update component imports and props
import { UploadButton, UploadDropzone } from '@uploadthing/react';
<UploadButton endpoint="imageUploader" />
<UploadDropzone endpoint="imageUploader" />import { UploadButton, UploadDropzone } from '@uploadkitdev/react';
<UploadButton route="imageUploader" />
<UploadDropzone route="imageUploader" />Step 4: Update environment variables
# Remove UploadThing variables
UPLOADTHING_SECRET=...
UPLOADTHING_APP_ID=...
# Add UploadKit variable (server-side only — never exposed to browser)
UPLOADKIT_API_KEY=uk_live_xxxxxxxxxxxxxxxxxxxxxStep 5: Update the API route path
If you have hardcoded the UploadThing API route path anywhere (e.g., in the UploadThing client or custom fetch calls):
# UploadThing
/api/uploadthing
# UploadKit
/api/uploadkitStep 6: Test the upload flow
- Start your dev server
- Try a file upload through your UI
- Verify the file appears in your UploadKit dashboard at app.uploadkit.dev
- Check that
onUploadCompletefires and your database is updated
What's new in UploadKit
Beyond the migration, UploadKit adds several capabilities that UploadThing does not offer:
BYOS (Bring Your Own Storage) — Use your own S3 or Cloudflare R2 bucket. Your files never touch UploadKit's infrastructure, but you still get the full SDK and dashboard experience.
More generous free tier — 5 GB storage, 2 GB bandwidth, and 1,000 uploads per month on the free plan. No credit card required.
CSS variable theming — One global CSS override styles all components. No need to copy class lists to every component.
Open-source SDK — @uploadkitdev/core, @uploadkitdev/react, and @uploadkitdev/next are all MIT licensed and published on npm.
Need help with your migration? Open a GitHub discussion or reach out at support@uploadkit.dev.
After migration: quickstart reference
Once migrated, you can follow the Quickstart guide for a clean reference of the full UploadKit setup.