Bring Your Own Storage (BYOS)
Use your own S3 or R2 bucket with zero frontend changes. No vendor lock-in.
What is BYOS?
BYOS (Bring Your Own Storage) lets you use the UploadKit SDK and React components while storing files in your own S3-compatible bucket. No vendor lock-in.
UploadKit Cloud vs self-hosted BYOS
- UploadKit Cloud (this dashboard,
uk_live_*/uk_test_*keys): files always land on UploadKit's managed R2 + CDN. There is no BYOS toggle in project settings. - Self-hosted BYOS (open-source path): you run
createUploadKitHandlerin your app with astorageadapter. Same components on the frontend; credentials live in your server environment only.
Pick one path per deployment — they are not mixed inside a single Cloud project.
In managed Cloud mode, files go to UploadKit's infrastructure. In BYOS mode, they go to your bucket — but the developer experience is identical. The same UploadButton, UploadDropzone, and useUploadKit work in both modes. Your frontend code never changes.
Setup
Configure BYOS in createUploadKitHandler by passing a storage object with your bucket credentials:
import { createUploadKitHandler } from '@uploadkitdev/next';
import type { FileRouter } from '@uploadkitdev/next';
const router = {
imageUploader: {
maxFileSize: '4MB',
allowedTypes: ['image/*'],
onUploadComplete: async ({ file }) => {
console.log('Stored in your R2 bucket:', file.url);
},
},
} satisfies FileRouter;
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!,
},
});R2_ACCOUNT_ID=your_cloudflare_account_id
R2_ACCESS_KEY_ID=your_r2_access_key_id
R2_SECRET_ACCESS_KEY=your_r2_secret_access_key
R2_BUCKET_NAME=your-bucket-nameGet your R2 credentials from the Cloudflare dashboard → R2 → Manage API Tokens.
import { createUploadKitHandler } from '@uploadkitdev/next';
import type { FileRouter } from '@uploadkitdev/next';
const router = {
documentUploader: {
maxFileSize: '16MB',
allowedTypes: ['application/pdf'],
},
} satisfies FileRouter;
export const { GET, POST } = createUploadKitHandler({
router,
storage: {
region: process.env.AWS_REGION!,
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
bucket: process.env.AWS_S3_BUCKET!,
},
});AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
AWS_S3_BUCKET=my-upload-bucketCreate an IAM user with s3:PutObject, s3:GetObject, s3:DeleteObject, and s3:AbortMultipartUpload permissions on your bucket.
import { createUploadKitHandler } from '@uploadkitdev/next';
import type { FileRouter } from '@uploadkitdev/next';
const router = {
fileUploader: {
maxFileSize: '50MB',
allowedTypes: ['*/*'],
},
} satisfies FileRouter;
export const { GET, POST } = createUploadKitHandler({
router,
storage: {
endpoint: process.env.MINIO_ENDPOINT!, // e.g. "http://localhost:9000"
region: 'us-east-1', // MinIO requires a region value, even though it's self-hosted
accessKeyId: process.env.MINIO_ACCESS_KEY!,
secretAccessKey: process.env.MINIO_SECRET_KEY!,
bucket: process.env.MINIO_BUCKET!,
},
});MINIO_ENDPOINT=http://localhost:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET=uploadsSupported providers
Any S3-compatible object storage works with BYOS:
| Provider | endpoint | region |
|---|---|---|
| Cloudflare R2 | https://<ACCOUNT_ID>.r2.cloudflarestorage.com | auto |
| AWS S3 | (omit — uses AWS default) | us-east-1 (or your region) |
| DigitalOcean Spaces | https://<region>.digitaloceanspaces.com | <region> (e.g. nyc3) |
| MinIO | http://your-minio-host:9000 | any string (e.g. us-east-1) |
| Wasabi | https://s3.wasabisys.com | us-east-1 |
| Backblaze B2 | https://s3.us-west-004.backblazeb2.com | us-west-004 |
The endpoint field is only required when using non-AWS providers. For standard AWS S3, omit it and the SDK uses the default regional endpoint.
Zero frontend changes
The storage configuration lives entirely in your server-side file router handler. Your React components and the REST API surface are completely unchanged:
// This code is identical in managed mode and BYOS mode
<UploadDropzone
route="imageUploader"
onUploadComplete={(files) => {
// files[0].url points to YOUR bucket, not UploadKit's managed storage
saveToDatabase(files[0].url);
}}
/>The only difference is where files end up — in your bucket, using your credentials.
Security model
BYOS credentials never touch the browser. Here's why that's safe:
- Your storage credentials (
accessKeyId,secretAccessKey) live in server-side environment variables - When a client requests an upload, your server uses those credentials to generate a presigned PUT URL
- The presigned URL is time-limited (1 hour), scoped to a single object key, and signed by your credentials
- The client uses the presigned URL to upload — it never sees your credentials
Browser Your server Your S3 bucket
│ │ │
│ "Upload imageUploader" ────►│ │
│ │ Uses your credentials │
│ │ to generate presigned URL ───►│
│ ◄── presigned URL ──────────│ │
│ │ │
│ PUT file ───────────────────┼───────────────────────────────►│
│ (using presigned URL) │ │
│ │ │
│ "Done" ─────────────────────►│ │Never pass accessKeyId or secretAccessKey to the client. The storage config belongs in server-side code only — never in a client component or a NEXT_PUBLIC_ environment variable.
Managed vs BYOS: when to use which
| Managed (default) | BYOS | |
|---|---|---|
| Setup time | Zero — works out of the box | ~10 minutes bucket config |
| Storage location | UploadKit's Cloudflare R2 | Your own bucket |
| Credential management | UploadKit handles it | You manage IAM/API keys |
| Data sovereignty | Shared infrastructure | Full control |
| Compliance (HIPAA, GDPR) | Check UploadKit DPA | Your existing compliance |
| CDN | Included (Cloudflare) | Configure separately |
| Cost | Included in UploadKit tier | Your provider's storage rates |
| Vendor lock-in | Tied to UploadKit storage | Switch providers freely |
Use managed when: you want zero infrastructure overhead, you're comfortable with shared storage, and you're on the Free or Pro tier.
Use BYOS when: you need data sovereignty, have existing storage infrastructure, are subject to compliance requirements, or want to avoid UploadKit storage costs for high-volume use.
See the Security page for details on how UploadKit protects your storage credentials and validates uploads in BYOS mode.