← Back to Blog
Guide forms file upload static site

How to Accept File Uploads From a Form Without a Backend

Résumés, screenshots, and portfolio PDFs need somewhere to go. Two attributes, one common mistake that silently sends empty files, and what to watch for.

JW

Jason Warner

July 30, 2026

How to Accept File Uploads From a Form Without a Backend

Text fields are the easy part. The moment someone needs to attach a résumé or a screenshot of the bug they're reporting, the standard advice is to go set up object storage, generate presigned URLs, and write an upload handler.

For a careers page that gets four applications a month, that's an absurd amount of infrastructure. Here's the version without it.

The Markup

Two changes to an ordinary form:

<form action="https://api.bluejayrelay.com/f/frm_yourtoken"
      method="POST"
      enctype="multipart/form-data">

  <label for="name">Name</label>
  <input id="name" name="name" required>

  <label for="email">Email</label>
  <input id="email" name="email" type="email" required>

  <label for="resume">Résumé (PDF, up to 10 MB)</label>
  <input id="resume" name="resume" type="file" accept=".pdf,.doc,.docx" required>

  <button type="submit">Apply</button>
</form>

enctype="multipart/form-data" is the one that matters. Leave it off and the browser sends the file name as a plain text value while the bytes stay on the applicant's machine. The form submits, you get a success response, the field is right there in the submission, and the attachment is empty. It's a maddening bug precisely because nothing appears to fail — and it's the cause about nine times out of ten when someone tells me uploads aren't working.

The accept attribute filters the file picker, which is a nice courtesy. It is not a security control. Anything can be posted regardless of what you put there.

For several files at once, add multiple and name the field like a plural:

<input name="screenshots" type="file" accept="image/*" multiple>

Each file arrives as its own attachment on the same submission, tagged with the field it came from.

Uploading With fetch

FormData handles files automatically, as long as you don't help it:

const form = document.querySelector('form');

form.addEventListener('submit', async (e) => {
  e.preventDefault();
  const data = new FormData(form);          // files included

  const res = await fetch(form.action, {
    method: 'POST',
    body: data,
    headers: { Accept: 'application/json' } // and nothing else
  });
});

The important line is the one that isn't there. Never set Content-Type yourself. Multipart encoding needs a boundary string the browser generates, and writing 'Content-Type': 'multipart/form-data' by hand produces a request with no boundary that the server can't parse. Leave it alone and it works.

Size, Realistically

Uploads are capped per submission — 10 MB on the free tier, more on paid plans. Over the limit gets rejected outright rather than truncated, so you get a real error instead of a corrupt file.

In practice: PDFs and documents are almost never a problem. Phone photos are, routinely — a modern phone camera produces 5–12 MB per image, so a form accepting three screenshots will blow past 10 MB without anyone doing anything unusual. Video isn't a good fit for a form endpoint at all; ask for a link instead.

Put the limit in the label. Discovering it after a slow upload on a train is a bad experience, and "PDF, up to 10 MB" costs you four words.

Where the Files End Up

They're stored with the submission, so a job application arrives as one coherent record — name, email, cover note, and the résumé attached — rather than as an email pointing at a bucket you now have to administer. Everything else about the form still applies: notification emails, an autoresponder confirming receipt to the applicant, spam filtering, origin restrictions, CSV export of the text fields.

Handling Uploads Sensibly

Taking files from the public internet deserves a bit of care, and none of it is complicated.

Don't trust the filename — submitted names can contain path traversal sequences, and while they're sanitized for display and download, you shouldn't take a raw submitted name and write it to your own disk if you later export. Don't trust the content type either; the browser reports whatever it likes, and a .pdf can be anything. Don't re-serve uploads publicly on your own site, because a file from an anonymous form is not something you want in your origin's namespace. And keep spam protection on — upload endpoints attract abuse specifically because they cost storage, and the honeypot and content layers filter most of it before anything gets written.


Accept résumés and attachments without touching a storage bucket. Try Bluejay Forms free.

#forms #file upload #static site

Ready to collect submissions?

Point your form at Bluejay and get storage, spam filtering, and email notifications.

Start collecting free