File uploads for Remix and React Router v7
Use the UploadKit React components in a Remix or React Router v7 app and sign uploads in a route action. The action returns a presigned PUT URL, and the browser sends the file directly to Cloudflare R2 or any S3-compatible bucket.
Start a Remix or React Router upload project
The fastest path is the starter template. It is a React Router v7 app in framework mode with an UploadDropzone and a signing action.
1. Scaffold the app
npx create-uploadkit-app my-app --template remixIn an existing project, install the packages instead. npx uploadkit init does the install and wraps app/root.tsx for you, but see the note below about its generated route.
pnpm add @uploadkitdev/react @uploadkitdev/core2. Sign uploads in a route action
The template's action validates the body, builds a presigned PUT URL with the AWS SDK and returns { url, key, method }. It reads R2 credentials from the server environment, so the browser never sees them. Add authentication in this action before you ship.
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
export async function action({ request }: { request: Request }) {
if (request.method !== 'POST') {
return Response.json(
{ error: 'Method not allowed' },
{ status: 405, headers: { Allow: 'POST' } },
);
}
const raw = await request.json().catch(() => null);
if (!raw || typeof raw.filename !== 'string' || typeof raw.contentType !== 'string') {
return Response.json(
{ error: 'Body must be { filename: string, contentType: string }' },
{ status: 400 },
);
}
const client = new S3Client({
region: 'auto',
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
});
const key = `${crypto.randomUUID()}-${raw.filename}`;
// ContentType MUST match the client's PUT header exactly — mismatch → 403.
const url = await getSignedUrl(
client,
new PutObjectCommand({
Bucket: process.env.R2_BUCKET!,
Key: key,
ContentType: raw.contentType,
}),
{ expiresIn: 60 * 5 },
);
return Response.json({ url, key, method: 'PUT' as const });
}3. Render the dropzone
import { UploadDropzone, UploadKitProvider } from '@uploadkitdev/react';
export default function Index() {
return (
<UploadKitProvider endpoint="/api/sign">
<main>
<UploadDropzone
onUploadComplete={(results) => {
console.log('uploaded', results);
}}
onUploadError={(error) => {
console.error('upload error', error);
}}
/>
</main>
</UploadKitProvider>
);
}import '@uploadkitdev/react/styles.css';About uploadkit init. For Remix it creates app/routes/api.uploadkit.$.tsx and points the provider at /api/uploadkit. That route is a placeholder that answers with a 501 error until you write the forwarding logic. If you want a working endpoint today, copy the action above and point the provider's endpoint at it.
What UploadKit gives a Remix app
Remix and React Router use the React half of the SDK. The typed file-router handler is part of the Next.js package and is not used here.
React upload components
UploadDropzone, UploadButton and the rest of the more than 40 components from @uploadkitdev/react, with a stylesheet you import once.
Presigned direct-to-storage uploads
The action only signs. File bytes go from the browser to your bucket, which avoids server body limits.
Bring your own storage
The template signs against your R2 or S3-compatible bucket with credentials from process.env.
Works with revalidation
After a successful upload, call useRevalidator().revalidate() to refresh your loaders, as shown in the tutorial.
Our tutorial builds the same flow by hand with the AWS SDK, zod validation, per-user object keys and a file list loader. Read file uploads in Remix and React Router v7 if you want to understand each piece, or browse the component gallery for the UI side.
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
Is there a dedicated Remix adapter?
No. @uploadkitdev/next is the typed server handler, and it targets Next.js. In Remix and React Router v7 you use the React components from @uploadkitdev/react and write the signing endpoint yourself as a route action. The starter template ships one.
What does npx uploadkit init do in a Remix project?
It detects Remix or React Router (@remix-run/* or @react-router/dev), wraps the body of app/root.tsx in UploadKitProvider with endpoint="/api/uploadkit", creates app/routes/api.uploadkit.$.tsx, adds UPLOADKIT_API_KEY to .env and installs @uploadkitdev/react and @uploadkitdev/core. The generated route is a placeholder: it returns a 501 error until you implement server-side forwarding, so the generated app does not look configured when it is not.
Does it work with Remix v2 and React Router v7?
Yes. The detector treats both as the same target, and the starter template is a React Router v7 app in framework mode. The presigned URL pattern in our Remix tutorial is the same on both, only the import names change. Remix and React Router tutorial
Why not parse multipart form data in the action?
Parsing the upload in the action sends every byte through your server, which runs into platform body limits and makes progress reporting harder. A presigned URL keeps the file off your server: the action returns a short-lived URL and the browser uploads directly to storage. Presigned URLs vs server proxy
Can I use my own bucket?
Yes. The template action signs a PUT URL against Cloudflare R2 or any S3-compatible bucket, using credentials from your server environment. See the BYOS docs for the model. BYOS docs
What does it cost?
The free plan includes 5 GB storage, 2 GB bandwidth per month, a 4 MB maximum file size and 1,000 uploads per month. Pro is $15 per month and Team is $35 per month. See pricing
Add uploads to your Remix app
Start on the free plan: 5 GB storage, 2 GB bandwidth per month, no credit card.