Forms

Accepting File Uploads on a Static Site Contact Form

A static host can't catch a file upload on its own. Here are the three real ways to add one, and the exact HTML SimpleForm needs to receive files directly.

· The SimpleForm Team

You're building a static site — Astro, Hugo, plain HTML, whatever — and the contact form needs one more field: a resume upload, a screenshot, a signed PDF. A standard form sends text fine, but the moment you add a file input, you need something on the other end to receive, validate, and store that file. Static hosts like Netlify or GitHub Pages serve files; they don't run a process that can catch a multipart POST and write it to disk. Get this wrong and you end up either maintaining a small serverless function you didn't want, or quietly dropping every file a visitor tries to send.

A static site form is a plain HTML form served from a host with no application server behind it. SimpleForm is a hosted form backend: you point a form's action attribute at a SimpleForm endpoint, and it receives the POST — including any files — and emails or stores the submission without you writing or deploying server code.

This comes up more often than it looks like at first. A careers page wants a resume. A support form wants a screenshot of the bug. An onboarding form wants a signed PDF back from a client. In every one of those cases the text fields already work fine on a static host — name, email, message post through without trouble — and it's specifically the file field that stalls the project, because it's the one part of the form that needs a real backend decision behind it.

Why Can't a Static Site Handle File Uploads on Its Own?

Because static hosting only serves pre-built files — there's no long-running process listening for incoming POST requests, checking a file's type, enforcing a size limit, or writing anything to disk. Your browser sends a file input using the multipart/form-data spec (RFC 7578), and something on the receiving end has to parse that multipart body, pull the file out of it, and decide where it goes. A static host has nothing there to do that parsing.

That's true whether the form is a job application, a support ticket with a screenshot, or a signup form asking for a signed contract. The HTML side is trivial — one attribute and one input tag. The receiving side is the whole problem, and it's exactly the part a static host was never built to do.

What Are the Three Ways to Add File Uploads to a Static Form?

There are three practical patterns, and they trade off setup effort against how much infrastructure you end up running yourself.

MethodSetup effortExtra infrastructure you runTypical size cap
Base64-encode and emailLow — client-side JS onlyNone~10 MB (encoding adds about 33% overhead)
Presigned URL to object storageMedium to highA signing function, a bucket, CORS rulesWhatever your bucket policy allows
Hosted form backend with native multipart supportLow — one HTML attributeNoneSet by the provider's plan (SimpleForm: 5 MB Pro, 25 MB Agency)

Base64-in-email works for a quick prototype but bloats the payload and runs into email attachment limits fast — most inboxes cap attachments well under 25 MB once the base64 overhead is added on top of the original file.

A presigned-URL flow to S3 or Cloudflare R2 scales best for large files, but walk through what it actually takes to build: the browser asks a small serverless function for a signed upload URL, the function checks the request and returns a short-lived URL scoped to one object key, the browser then PUTs the file straight to the bucket using that URL, and only the resulting object key gets submitted through your normal form field. That's a function to write and deploy, a bucket to create, a CORS policy to configure on the bucket, and an expiry window to pick for the signed URL — real infrastructure for what started as a static site with one contact form.

If you don't want to run and secure a signed-URL function just to accept an attachment, a hosted form backend that accepts multipart/form-data directly skips that infrastructure: you add one attribute to the form you already have. See the file upload section of the docs for the exact endpoint format and response codes.

How Do You Set Up File Uploads with SimpleForm?

Once you have a form pointed at a SimpleForm endpoint, adding a file field takes a handful of small changes. Worth deciding before you touch the HTML: what file types you actually need, because the browser's accept attribute can filter the picker for the visitor, but that's a convenience, not a security boundary — the server still has to be the one enforcing real size and type limits, which is exactly what the hosted endpoint does for you.

  1. Add enctype="multipart/form-data" to your existing form tag.
  2. Add an <input type="file" name="resume"> field (name it for whatever you're collecting).
  3. Leave the form's action attribute pointed at your existing SimpleForm endpoint — no URL changes needed.
  4. Submit a real test file from the live page, not a form builder preview.
  5. Open the Submissions page in your dashboard and confirm the file is attached to that entry and downloadable.
  6. On Pro or Agency, optionally add a webhook so a new file submission also posts to Slack or Discord.

Which Plan Do You Need for File Uploads?

File uploads require Pro or Agency — the Free plan does not include them. Pro allows up to 5 MB per file for $9/month ($90 billed yearly), and Agency raises that to 25 MB per file for $29/month ($290 billed yearly). Full plan details, including submission and form limits, are on the pricing page. The Free plan itself stays useful even without uploads: 100 submissions a month across 3 forms with 7 days of submission history, free forever with no credit card.

If file uploads are occasional — a careers page that gets a handful of resumes a month — $9/month is close to what you'd spend on object storage alone, without the time spent building and maintaining a signing function. If your form never needs a file field, stay on Free: the 100-submissions-a-month, 3-form limit costs nothing, and adding uploads later is a plan change, not a rebuild of the form.

What Happens If an Uploaded File Is Too Large?

The submission is rejected before anything is stored, and the endpoint returns a 402 response — the same code SimpleForm uses when you're over your monthly submission limit. The rest of that submission's fields aren't saved either, so a rejected file means the whole entry is gone, not just the attachment. Separately, SimpleForm caps IP-based submissions at 10 per hour per endpoint and returns a 429 status code over that limit, which is unrelated to file size but worth handling the same way.

If you're submitting over AJAX with an Accept: application/json header, check the response status before assuming success. A generic 'something went wrong' message on a 402 tells a visitor nothing; naming the actual limit tells them what to do next.

Get Your First File Upload Working

Sign up free, wire up your form's text fields first, and confirm submissions are landing in your dashboard. When you're ready for the file input, upgrade to Pro and add the two HTML changes above — it's a plan change on an existing form, not a new integration.

Frequently asked questions

Yes. Add enctype="multipart/form-data" to the form tag and a file input field, upgrade the form's account to Pro or Agency, and resubmit — no changes to the endpoint URL or your existing fields are needed. A submission with a file attaches to the same dashboard entry as your text fields.

Any type your file input field allows — SimpleForm does not restrict by file extension. Use the HTML accept attribute, such as accept=".pdf,.doc", if you want to guide visitors toward a specific format before the file reaches the server.

Files are retained on the same schedule as the rest of your submission data, per your plan's retention window of 7 to 365 days, and stay accessible from your dashboard until deleted or the retention period expires. You can delete any submission, file included, at any time.

Yes, on Pro and Agency plans. Add a webhook URL to the form and each submission, file included, fires a POST with the submission data to that URL; both Slack and Discord accept simple webhook payloads for this.

The submission is rejected before it's stored and the endpoint returns a 402 response, the same code used for exceeding your monthly submission limit. Handle this in your AJAX code by checking for a non-success response and showing the visitor a clear message instead of a silent failure.

Ship a working form in five minutes. Point your form's action at a SimpleForm endpoint and submissions land in your inbox and dashboard straight away. Start free or read the docs.

More from the blog