Skip to content
FFormhook
Back to blog

Blog

File uploads on a static site: your real options

· 3 min read

Text fields are easy - the hard question arrives with the brief that says "and applicants should attach a CV." A static site can't receive a file; there's no server to receive it. Here are the actual options, from worst to best.

Option 1: "email it to us" (the surrender)

Ask users to send attachments by email. It works, technically, the way a fax machine works: the file arrives disconnected from the form data, into an inbox, with no structure and no size control. For anything recurring - job applications, support attachments, print files - it turns into manual filing forever.

Option 2: direct-to-cloud-storage uploads (the DIY route)

Front-end JavaScript can upload straight to object storage (S3-style presigned URLs). It's a legitimate architecture - and a surprising amount of work to do safely: something has to generate those presigned URLs (so you're writing a serverless function after all), you're implementing size and type validation yourself, connecting the uploaded file to the form submission it belongs to, and deciding where in the world that bucket lives, which matters the moment the uploads contain personal data like CVs.

Choose this when uploads are your product. For "attach a file to a form," it's a lot of plumbing.

Option 3: multipart through a form backend (the boring, correct one)

HTML has supported file fields since roughly forever:

<form action="https://formhook.app/f/fh_your-key"
      method="POST" enctype="multipart/form-data">
  <input type="email" name="email" required>
  <textarea name="message" required></textarea>
  <input type="file" name="attachment">
  <button type="submit">Send</button>
</form>

The one thing people forget is enctype="multipart/form-data" - without it the browser sends the filename instead of the file. With a form backend receiving the POST, the file is stored with its submission: open the enquiry in the dashboard, the attachment is right there. Size and type limits are enforced at the endpoint, and spam protection applies to the whole submission, files included.

In Formhook, file uploads are available on the Pro plan and up, and - the part that matters for European sites - uploaded files are stored in Cloudflare R2's EU region, so a CV attached by an EU applicant gets the same data-residency answer as the text fields: it never leaves the EU. (Storage details in the docs.)

Practical rules whichever option you pick

  1. Constrain the input. Use the accept attribute (accept=".pdf,.png,.jpg") as UX, but treat it as advisory - real validation happens at the endpoint.
  2. Tell users the limit before they hit it. "Max 10 MB" next to the field beats an error after a slow upload.
  3. Files are personal data too. A CV is arguably more sensitive than the form fields around it. Retention, deletion, and export policies must cover attachments, and your privacy policy's storage-location claim includes them. (See the GDPR checklist.)
  4. Keep the no-JS path working. A plain multipart form posts fine without any script; add AJAX polish on top, not instead.

The pattern for static sites is settled: the site stays static, the form backend receives the multipart POST, and the file lands next to the message it came with. The form is still one attribute - plus one enctype.

Ship a working form in one line

EU-hosted, submissions kept forever, push notifications on every tier.

Start free

No credit card · read the docs

Keep reading