Vibe Coding Uploads

Adding File Uploads to Your Vibe Coded App

Your app has users now, and someone wants to upload a profile photo, document, receipt, or attachment. It looks like a small feature, but a careless implementation can grow storage costs, expose a private file, or accept content that does not match its name. This guide uses hosted object storage as a practical default and shows the validation, authorization, retention, and recovery decisions that still remain yours.

Last reviewed: Aug 28 2026

A glowing file icon moving along a track through a corridor of layered checkpoint gates toward a storage vault, with one gate lit in warm amber where the file is scanned before it's allowed to pass.
A locked gate stops the wrong file — an unlisted door only slows down whoever finds it.

Why Uploads Deserve Extra Scrutiny

A file upload is one of the easiest features to ask AI for, and one of the easiest to get wrong in ways that don't show up until later. AI can produce a working file picker, an "Upload successful" message, and a photo that displays back on the page — and it will look finished. What's easy to miss: whether the file went somewhere that scales, whether "private" actually means private, and whether the app checked what the file really is instead of trusting what its name or extension claims.

A hosted service can reduce infrastructure work, but it does not make an upload safe by itself. Confirm that authorization is enforced on every read, replace, and delete; validate the received content rather than trusting the browser; set size and retention limits; and decide whether malware scanning or manual review is required for the file type and audience.

Use Object Storage as the Default for User Files

For most web apps, keep user file bytes in managed object storage and store only a file key plus metadata in the application database. Database blobs and server-proxied uploads can be valid in specialized designs, but they require explicit size, streaming, scanning, backup, and capacity decisions. Do not let AI choose either path accidentally.


The Safe Default: A Hosted Storage Service

A hosted storage service stores and serves the bytes and can enforce quotas and access rules. You must configure those controls: defaults differ, and a public bucket or URL remains public regardless of how random its name looks. Your app normally keeps a file key and trusted metadata in its database.

Direct-to-provider uploads can keep large buffers out of your application server, but callbacks, metadata, authorization, scanning, and deletion still form part of your code path. Choose a provider only after checking the required access model and current plan.


The Prompt to Ask For

Name the hosted service explicitly so AI doesn't default to writing a custom upload handler, and ask for access control and validation in the same prompt so neither is left as an afterthought:

Prompt

Add file upload to my app using [Supabase Storage / Cloudinary / UploadThing] for [profile photos / documents / attachments]. Store the object in managed storage and keep only its provider key plus trusted metadata in my database. Include: (1) direct or signed upload where appropriate, (2) maximum size, count, and allowed types enforced by the provider or server, with server-side content inspection rather than only filename, MIME header, or the file picker's accept attribute, (3) private-by-default access with an expiring signed URL or authenticated delivery when the chosen provider and plan support it, (4) owner checks for read, replace, and delete, (5) a malware-scanning or quarantine decision appropriate to the file type, and (6) retention, cleanup, backup, and cost controls. Identify every provider setting, plan dependency, and assumption you cannot verify.

When the AI returns code, check for these things before you consider it done:

  1. The chosen storage boundary is explicit — normally the database stores a provider key and metadata while object storage holds the bytes.
  2. A size limit and type restriction are enforced by the provider or your server, not just suggested by the file picker — a browser's accept attribute is a hint to the operating system's file dialog, not a check on what actually gets sent.
  3. Private files use signed or access-controlled URLs, not a public bucket with an unguessable name — the next section explains why that distinction matters.
  4. Only the uploading user can replace or delete their own file — the same ownership check you'd expect on any other user data.

Why a Hidden File URL Isn't the Same as a Private File

It's tempting to treat a long, random-looking file URL as private — nobody would guess it, so it feels safe. Don't rely on this. If the file lives in a public bucket, anyone who obtains that URL — from a shared link, a browser history, a referrer header, or a page that embeds the image — can view or download it forever, with no way for you to revoke access. An unguessable name changes who's likely to find the file; it does nothing to stop anyone who does.

Decide Public or Private Per File, Not Per App

A public marketing image and a user's private tax document are not the same kind of file, even if your app stores both in the same provider. Public assets — a logo, a shared blog header — can live in a public bucket with a plain URL. Anything tied to one user's account should default to private: access enforced by the storage provider's own rules (Supabase Storage policies, Cloudinary's signed delivery, UploadThing's access controls) and served through a URL that expires or that only resolves for the authenticated owner. Decide this per file type up front, not after a user finds someone else's file.

Test this the same way you'd test account isolation: upload a file as Account A, then try to fetch it directly — by URL, and by calling whatever endpoint serves it — while signed in as Account B, and again while signed out entirely. If the file was meant to be private, both attempts should fail. A file that only "looks" private because its URL is long isn't private — it's unlisted.

Prompt

Show me exactly how access is controlled for each file type my app stores. For anything tied to a single user's account, confirm the storage provider itself denies access to other users and to signed-out visitors — not just that the app doesn't link to the file. Give me a test: upload a file as one account, then show the requests I'd run to try to access it as a different account and while signed out, and what response each should return.


Validate What You Received, Not What Was Claimed

A file's name and extension are whatever the person uploading it typed, and the browser's reported content type is whatever the operating system guessed — neither is a guarantee of what the file actually is. A file named photo.jpg can contain anything. Ask AI to check the file's real content, not its label, before your app treats it as an image, a PDF, or anything else it displays or processes.


Keeping Storage and Cost Under Control

Unlike most features, uploads add to a bill that grows with usage and never shrinks on its own — every file kept is file storage you keep paying for, on every provider's free tier and beyond. Two habits keep this from surprising you:


Pre-Launch Uploads Checklist

Related Guides

Adding User Accounts to Your Vibe Coded App

Most uploads belong to a signed-in user — hosted auth, per-user data access, and the two-account isolation test this guide builds on.

Adding Payments to Your Vibe Coded App

The other feature where an AI shortcut has real consequences — hosted checkout, webhooks, and what to verify before real money moves.

Growing Your Vibe Coded App

Adding features, handling real users, and keeping an eye on hosting and storage costs as your app grows.

Sanitizing Code and Data Before Sending to AI

What to scrub before pasting code or credentials into an AI conversation — including storage keys.

Back to Home