Serverless file uploads with Supabase Storage and Next.js API routes
Secure, serverless file uploads in Next.js using Supabase Storage, with validation, size limits, and public access handling.
On this page
File uploads are where a lot of apps quietly become insecure. The pattern below keeps validation on the server and never trusts the client's file type.
Prefer signed uploads
For anything user-generated, hand the client a short-lived signed URL instead of proxying the bytes through your function.
export async function POST(request: Request) {
const { filename, contentType, size } = await request.json();
if (size > 5 * 1024 * 1024) {
return new Response("File too large", { status: 413 });
}
const path = `${crypto.randomUUID()}-${sanitize(filename)}`;
const { data, error } = await supabase.storage
.from("uploads")
.createSignedUploadUrl(path);
if (error) return new Response("Upload failed", { status: 500 });
return Response.json({ path, token: data.token, contentType });
}
The client uploads directly to storage, which keeps your function fast and cheap.
Validate on the server, always
The contentType a browser sends is a claim. Verify the actual bytes before making a file public.
- Check the magic number, not the extension
- Enforce a maximum size
- Generate the stored path yourself
Control public access
Buckets are private by default. Keep them that way and serve through signed download URLs.
const { data } = await supabase.storage
.from("uploads")
.createSignedUrl(path, 60 * 60);
Clean up after yourself
Uploads are storage costs. Delete what is no longer referenced.
| Event | Action |
|---|---|
| Upload replaces a file | Delete the previous object |
| Record deleted | Enqueue cleanup |
| Upload abandoned | Expire via a scheduled job |
Never trust a filename from the client. Generate your own identifiers.
Signed URLs, server-side validation, and a cleanup job are the whole checklist.
Continue reading
Related articles
How I built a fullstack authentication system with Next.js, Supabase, and Tailwind
Email sign-in, session handling, and protected routes in a Next.js App Router app, built on Supabase auth with a clean Tailwind interface.
Building a modern todo app with Zustand, Firebase, and Next.js
A real-time todo app with authentication, CRUD operations, and predictable global state, built with Next.js, Firebase, and Zustand.
Integrating Razorpay payments in Next.js: a step-by-step guide
Set up checkout, verify signatures on the server, and handle webhooks safely when integrating Razorpay with Next.js.