File uploads for Next.js
UploadKit is an upload SDK for the Next.js App Router. You define typed file routes in a Route Handler, drop in React upload components, and the browser sends files directly to storage with presigned URLs. Use UploadKit's managed storage or your own S3-compatible bucket.
Set up uploads in a Next.js App Router project
This follows the Next.js getting-started guide. It needs Next.js 14 or later and React 18 or later. To start from a ready project, run npx create-uploadkit-app my-app --template next. To add UploadKit to an existing project, run npx uploadkit init.
1. Install
pnpm add @uploadkitdev/next @uploadkitdev/react2. Define the file router
Every upload category lives in one Route Handler with its size limit, allowed types and server-side logic. The middleware runs on your server before an upload is authorised, and whatever it returns is passed to onUploadComplete as typed metadata.
import { createUploadKitHandler } from '@uploadkitdev/next';
import type { FileRouter } from '@uploadkitdev/next';
import { auth } from '@/lib/auth'; // your Auth.js or Clerk auth helper
const router = {
// Accept profile images up to 4 MB
imageUploader: {
maxFileSize: '4MB',
allowedTypes: ['image/*'],
onUploadComplete: async ({ file, metadata }) => {
console.log('Image uploaded by user:', metadata.userId, file.url);
},
},
// PDF / Word documents up to 16 MB, max 5 files at once
documentUploader: {
maxFileSize: '16MB',
maxFileCount: 5,
allowedTypes: ['application/pdf', 'application/msword'],
middleware: async ({ req }) => {
const session = await auth();
if (!session?.user?.id) {
throw new Error('Unauthorized');
}
// Anything returned here is passed to onUploadComplete as `metadata`
return { userId: session.user.id };
},
onUploadComplete: async ({ file, metadata }) => {
console.log('Document uploaded:', file.key, 'by', metadata.userId);
},
},
} satisfies FileRouter;
export const { GET, POST } = createUploadKitHandler({ router });3. Add the API key
# Server-side only. Never prefix this with NEXT_PUBLIC_
UPLOADKIT_API_KEY=uk_live_xxxxxxxxxxxxxxxxxxxxx4. Wrap the layout with the provider
import { UploadKitProvider } from '@uploadkitdev/react';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<UploadKitProvider endpoint="/api/uploadkit">
{children}
</UploadKitProvider>
</body>
</html>
);
}5. Use a component
import { UploadDropzone } from '@uploadkitdev/react';
export default function DocumentsPage() {
return (
<UploadDropzone
route="documentUploader"
onUploadComplete={(files) => {
console.log('Documents uploaded:', files);
}}
/>
);
}What the Next.js SDK includes
@uploadkitdev/next is the server half and @uploadkitdev/react is the UI half.
React upload components
UploadButton, UploadDropzone and UploadModal work in App Router pages, and the SDK exports more than 40 components in total.
Type-safe file routes
Declare the router with satisfies FileRouter and the route prop on every component is typed to your route names.
Presigned direct-to-storage uploads
Your handler authorises the request, then the browser uploads the file to storage without proxying the bytes through Next.js.
Bring your own storage
Pass a storage config to createUploadKitHandler to use Cloudflare R2, AWS S3, MinIO or another S3-compatible bucket. The frontend code does not change.
Typed components
Bind the components to your router type once, then import them from that module.
import { generateReactHelpers } from '@uploadkitdev/react';
import type { FileRouter } from '@/app/api/uploadkit/route';
// Re-export typed components — route prop is now typed to your route names
export const { UploadButton, UploadDropzone, UploadModal, useUploadKit } =
generateReactHelpers<FileRouter>();Your own bucket
In self-hosted BYOS mode you supply either an apiKey or a storage object, not both. Credentials stay in your server environment. The managed UploadKit Cloud path and the self-hosted path are chosen per deployment, and they are not mixed inside one Cloud project.
export const { GET, POST } = createUploadKitHandler({
router,
storage: {
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
region: 'auto',
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
bucket: process.env.R2_BUCKET_NAME!,
},
});For the details of the S3 side of this flow, read how to upload files to Cloudflare R2 in Next.js, or the longer walkthrough with sessions and a database in file uploads in Next.js with Auth.js and MongoDB.
Free
5 GB storage, 2 GB bandwidth per month, 4 MB max file, 1,000 uploads per month, 2 projects.
Pro — $15/mo
100 GB storage, 200 GB bandwidth, 512 MB max file, 50,000 uploads per month, 10 projects.
Team — $35/mo
1 TB storage, 2 TB bandwidth, 5 GB max file, 500,000 uploads per month, 50 projects.
Enterprise
Custom limits. See the pricing page.
Frequently asked questions
Does UploadKit work with the Next.js Pages Router?
No. The SDK and the CLI target the App Router. When uploadkit init finds a Pages Router project (pages/_app) without an app directory, it stops with the message "Pages Router not supported — migrate to App Router". The docs list Next.js 14 or later with the App Router as the prerequisite. Next.js setup guide
How do I upload to S3 from Next.js with UploadKit?
Pass a storage object (bucket, region, accessKeyId, secretAccessKey, and an endpoint for non-AWS providers) to createUploadKitHandler. Files go to your bucket and the React components stay the same. BYOS works with any S3-compatible storage; the docs show Cloudflare R2, AWS S3 and MinIO. BYOS guide
Do uploads pass through my Next.js server?
No for the file bytes. Your route handler authorises the upload and the browser sends the file straight to storage with a presigned URL, so the file bytes never count against the request body limit of your hosting platform. See the post on presigned URLs versus server proxy uploads for the trade-offs. Presigned URLs vs server proxy
Is the route typed end to end?
Yes. Define the router with satisfies FileRouter and pass its type to generateReactHelpers. The route prop of UploadButton, UploadDropzone and UploadModal is then typed to your route names.
Which components do I get?
UploadButton, UploadDropzone and UploadModal are covered in the Next.js guide, and the SDK exports more than 40 React components in total. Each one reads the endpoint from UploadKitProvider. Browse components
What does it cost?
The free plan includes 5 GB storage, 2 GB bandwidth per month, a 4 MB maximum file size, 1,000 uploads per month and 2 projects. Pro is $15 per month and Team is $35 per month. With self-hosted BYOS your files and storage bill stay with your own provider. See pricing
Add uploads to your Next.js app
Start on the free plan: 5 GB storage, 2 GB bandwidth per month, no credit card.