Skip to content
FormSubmit

File uploads

HTML file upload form: single and multiple file uploads without a server

Build an HTML file upload form with single or multiple file uploads, accept filters and size limits. Files are stored privately, with no backend code.

4 min readUpdated Oct 8, 2026

To build a file upload form in HTML without a server, add enctype="multipart/form-data" and a file input, and set the form's action to a FormSubmit endpoint. Add the multiple attribute to let visitors upload several files at once. FormSubmit stores each file privately, links it in your notification email and dashboard, and passes signed links to your integrations.

Basic upload form

upload.html
<form action="https://formsubmit.app/f/YOUR_FORM_ID" method="POST" enctype="multipart/form-data" class="fs-form">
  <div class="fs-field">
    <label for="fs-name" class="fs-label">Name</label>
    <input id="fs-name" type="text" name="name" required class="fs-input">
  </div>
  <div class="fs-field">
    <label for="fs-email" class="fs-label">Email</label>
    <input id="fs-email" type="email" name="email" required class="fs-input">
  </div>
  <div class="fs-field">
    <label for="fs-files" class="fs-label">Files</label>
    <input id="fs-files" type="file" name="files" multiple required class="fs-input">
  </div>
  <div class="fs-field">
    <label for="fs-note" class="fs-label">Note</label>
    <textarea id="fs-note" name="note" rows="5" class="fs-input"></textarea>
  </div>
  <!-- Honeypot: leave this hidden field empty to catch bots -->
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
  <!-- Optional: where to send people after submitting -->
  <!-- <input type="hidden" name="_redirect" value="https://yoursite.com/thanks"> -->
  <button type="submit" class="fs-button">Send</button>
</form>

<style>
  .fs-form { max-width: 32rem; margin: 0 auto; display: grid; gap: 1.1rem; font-family: system-ui, sans-serif; }
  .fs-field { display: grid; gap: 0.35rem; border: 0; padding: 0; margin: 0; }
  .fs-label { font-size: 0.875rem; font-weight: 600; color: #18181b; }
  .fs-input { width: 100%; box-sizing: border-box; border: 1px solid #d4d4d8; border-radius: 0.5rem; padding: 0.6rem 0.75rem; font: inherit; font-size: 0.9rem; }
  .fs-input:focus { outline: 2px solid #6366f155; border-color: #6366f1; }
  .fs-choice { display: flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; }
  .fs-help { font-size: 0.75rem; color: #71717a; margin: 0; }
  .fs-button { background: #18181b; color: #fff; border: 0; border-radius: 0.5rem; padding: 0.7rem 1rem; font-weight: 600; cursor: pointer; }
  .fs-button:hover { background: #27272a; }
</style>

Key details:

  • method="POST" and enctype="multipart/form-data" are both required. Without the enctype, the browser only sends the file name.
  • Each file input needs a name. That name labels the attachment in your email and dashboard.
  • Other fields (name, email, message) are sent in the same request, so one submission holds both the files and the details.

A minimal version looks like this:

html
<form action="https://formsubmit.app/f/YOUR_FORM_ID" method="POST" enctype="multipart/form-data">
  <label>Email <input type="email" name="email" required></label>
  <label>Resume <input type="file" name="resume" accept=".pdf,.doc,.docx" required></label>
  <input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">
  <button type="submit">Upload</button>
</form>

HTML form with multiple file upload

To accept several files in one field, add the multiple attribute. Visitors can then shift- or ctrl-click several files in the picker, or drop several on the input:

html
<form action="https://formsubmit.app/f/YOUR_FORM_ID" method="POST" enctype="multipart/form-data">
  <label>Your email <input type="email" name="email" required></label>

  <!-- One input, many files -->
  <label>Photos <input type="file" name="photos" accept="image/*" multiple></label>

  <!-- Separate inputs for different documents -->
  <label>ID document <input type="file" name="id_document" accept=".pdf,image/*"></label>
  <label>Proof of address <input type="file" name="proof_of_address" accept=".pdf"></label>

  <input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">
  <button type="submit">Send files</button>
</form>

How multiple files are sent and named

With multiple, the browser sends one multipart part per selected file, and every part uses the input's name. FormSubmit stores each part as a separate attachment, labelled with that field name. So three photos in the example above show up as three files under photos.

You'll often see name="photos[]" in tutorials. The brackets are a convention for server frameworks like PHP, which need them to collect several values under one key. FormSubmit doesn't need them. Both photos and photos[] keep every file, but the files are labelled with the name exactly as written, so photos reads better in your email and dashboard.

When should you use one multiple input, and when separate inputs?

UseWhen
One input with multipleThe files are the same kind of thing: photos, screenshots, portfolio pieces
Separate named inputsEach file means something different: resume vs cover letter, front vs back of an ID

Multiple file upload limits

  • Up to 20 files per submission.
  • Each file must be within your plan's per-file limit (see below).
  • The whole request has to stay under about 4.5 MB when you post plain HTML. That total is what most often trips up multiple-file forms: five 1.5 MB phone photos are already too much. For bigger batches, use direct uploads.

You can't cap the number of files with HTML attributes alone. If you need to, check input.files.length in a small script before submitting.

Filter file types with accept

The accept attribute filters what the file picker shows. It takes extensions, MIME types or wildcards, separated by commas:

html
<input type="file" name="resume" accept=".pdf,.doc,.docx">
<input type="file" name="photos" accept="image/*" multiple>
<input type="file" name="logo" accept="image/png,image/svg+xml">

accept is a usability hint, not a security control. Visitors can switch the picker to "All files", and bots ignore it entirely. FormSubmit accepts any file type, stores it privately and always serves files other than images and PDFs as downloads rather than opening them in the browser.

File size limits

PlanPer fileTotal storage
Free10 MB100 MB
Starter10 MB2 GB
Pro25 MB10 GB
Business25 MB25 GB

Plain multipart submissions are capped at about 4.5 MB per request in total by our serverless infrastructure. Larger requests fail with payload_too_large, and a single file over your plan's limit fails with file_too_large.

For larger files, or several files that add up past 4.5 MB, upload directly from the browser to storage and submit references to them instead. Each file can then be as large as your plan allows. See direct uploads for the code, which loops over every file in every input, so multiple works without changes. If you'd rather not write it yourself, FormSubmit also serves an optional helper script at https://formsubmit.app/formsubmit.js that adds AJAX submission and direct uploads to an existing form. Check the Install tab in your dashboard for the current snippet. For more on sending forms from JavaScript, see submitting a form with JavaScript.

To warn visitors before they hit a limit, check sizes in the browser:

html
<script>
  document.querySelector("form").addEventListener("submit", (e) => {
    const files = [...e.target.querySelectorAll('input[type="file"]')].flatMap((i) => [...i.files]);
    const total = files.reduce((n, f) => n + f.size, 0);
    if (total > 4.5 * 1024 * 1024) {
      e.preventDefault();
      alert("Those files are too large to send together. Please send fewer or smaller files.");
    }
  });
</script>

A job application form

The job application template is a good starting point: name, email, phone, LinkedIn, position, resume upload and cover letter.

Where do the files go?

  • Your notification email lists each attachment as a link.
  • In the dashboard, open a submission to preview images and PDFs or download any file.
  • Webhooks receive a files array with each file's field, filename, url, size and contentType. Slack, Discord, Airtable and Google Sheets receive signed links.

Files are private, count toward your storage quota, and are deleted with their submission, either manually or through retention. Files attached to plain HTML submissions that are flagged as spam aren't stored. For security considerations, from file type checks to retention, read secure file upload forms.

Frequently asked questions

How do I upload multiple files with an HTML form?

Add the multiple attribute to a file input, for example input type="file" name="photos" multiple, and keep enctype="multipart/form-data" on the form. The browser sends every selected file under the same field name, and FormSubmit stores each one as a separate attachment on the submission.

Should I name a multiple file input photos or photos[]?

Either works with FormSubmit. A plain name like photos is cleaner, and it's how the files are labelled in your email and dashboard. The [] suffix is only needed by server frameworks such as PHP that require it to collect several values.

What's the maximum file size?

10 MB per file on Free and Starter, 25 MB on Pro and Business. Plain multipart posts are limited to about 4.5 MB per request in total, so use direct browser uploads for larger files.

How many files can one submission include?

Up to 20 files per submission, within your plan's per-file size and total storage limits.

Are uploaded files public?

No. They're stored privately and accessed via your dashboard or signed links that expire after 7 days.

Keep reading

Your form backend is 60 seconds away

Sign up with Google or email, create a form, paste the endpoint. Free forever for small sites — no credit card.