splitforms.com

HTML · FORM CONTROL

HTML file upload: input type=file with a live demo

Last reviewed

To upload a file with HTML, put <input type="file" name="file"> inside a <form method="post" enctype="multipart/form-data">; without that enctype, only the filename is sent. Add accept (for example .pdf,image/*) to filter the picker and multiple to allow several files. The browser sends the bytes to the form's action URL, so a server or form backend must receive and store them.

  • 500 free total
  • No backend
  • Copy-paste ready
Live demo · nothing is uploaded

Pick files to see the name, type and size the browser reads from input.files.

The files stay in your browser. This demo only reads input.files.

Copy-paste code

HTML File Input (Upload) code you can paste today.

Replace YOUR_ACCESS_KEY with a key from your free splitforms dashboard. The form posts to one endpoint — submissions land in your inbox and dashboard with spam filtering built in.

Get free access key
file-input.html
<form action="https://splitforms.com/api/submit" method="POST" enctype="multipart/form-data">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">

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

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

  <!-- Files are stored on paid plans with Storage connected -->
  <label for="resume">Upload your resume (PDF or Word)</label>
  <input type="file" id="resume" name="resume" accept=".pdf,.doc,.docx" required>

  <!-- Honeypot: hidden from people, bots fill it in -->
  <input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off">

  <button type="submit">Send application</button>
</form>

Key facts

What does a file upload form need?

A file upload needs a <form> with method="post" and enctype="multipart/form-data", and an <input type="file"> with a name. Everything else is optional: accept and capture steer the picker, multiple allows several files, and size limits are up to your JavaScript and your server.

What does a file upload form need?
RuleWhat happens
enctype="multipart/form-data"Required. Without it the browser sends only the file's name, never its contents.
method="post"Required. A GET form puts the fields in the URL, and a file input adds only its filename.
acceptA hint for the file picker only. It is not checked on submit, so check the type again on the server.
multipleLets the user pick several files. Each one is sent as its own part under the same name.
captureWith an image or video accept such as image/*, phones open the camera: user for the front camera, environment for the rear. Desktop browsers ignore it.
Maximum file sizeThere is no HTML attribute for it. Check File.size (in bytes) in JavaScript and enforce the limit on the server.
No file chosenThe field is still sent, as an empty part with filename="".
fetch with FormDataNever set the Content-Type header yourself. The browser writes multipart/form-data with the boundary it used.

Sources: WHATWG HTML Standard: enctype · WHATWG HTML Standard: constructing the entry list · WHATWG HTML Standard: file upload state · MDN: Using FormData objects · MDN: Blob.size

On splitforms

What happens to uploaded files on splitforms?

Uploads are stored on paid plans (Pro at $5/month or Annual at $49/year) once the Storage integration is connected. On the Free plan the text fields are saved and the files are dropped; a fetch upload gets a note in the JSON response saying so.

What happens to uploaded files on splitforms?
Limitsplitforms
PlansPro or Annual with Storage connected. Free saves text fields only.
Files per submissionUp to 5
Size per fileUp to 5 MB
Notification emailUploads totaling 7 MB or less are also attached to the email. Every email and the dashboard carry download links.
WebhooksThe payload's files[] lists each upload with a signed download URL that is valid for 7 days.

Last checked against the splitforms upload code and llms-full.txt on 2 October 2026.

Attributes · <input type="file">

Which HTML File Input (Upload) attributes matter?

These attributes decide how the <input type="file"> behaves and what actually gets emailed to your inbox when someone submits.

HTML File Input (Upload) attributes and what each one does
AttributeWhat it doesDocs
acceptRestricts the picker to file types, e.g. ".pdf", "image/*", "image/png, image/jpeg". It's a hint, not a security check.MDN
multipleBoolean. Lets the user select more than one file; each one is sent as its own part under the same name.MDN
captureOn phones, with an image or video accept (e.g. accept="image/*"), opens the camera instead of the file picker: capture="environment" (rear) or capture="user" (front). Desktop browsers ignore it.MDN
nameThe key the file(s) are submitted under. Required for the upload to be sent.MDN
requiredBoolean. Blocks submit until a file is chosen.MDN

Common mistakes

HTML File Input (Upload) not working? Here's why.

Most broken forms come down to a few small mistakes with the <input type="file">. Check these before you ship, then forward every submission to Slack or a webhook.

  1. You need the right enctype

    Without enctype="multipart/form-data" on the <form>, the browser sends the filename as text but drops the file contents. File uploads silently fail.

  2. Use POST, never GET

    Files can only travel in a POST body. A GET form puts the fields in the URL query string and sends only the filename.

  3. accept is not validation

    It filters the file picker, but users can override it. Validate the type and size again on the server before storing anything.

Sources: WHATWG HTML Standard: <input type="file"> · MDN: <input type="file">

multipart/form-data

What does the browser send when you upload a file?

With multipart/form-data, every field becomes its own part between boundary lines, and a file part adds a filename and a Content-Type before the raw bytes. The browser picks a random boundary and writes it into the request's Content-Type header. Without that enctype, the same form sends access_key=YOUR_ACCESS_KEY&resume=cv.pdf: the filename, no file. For URL-encoded and JSON bodies, and for sending files with fetch, see how to send form data to an API.

upload-request.http
POST /api/submit HTTP/1.1
Host: splitforms.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxk

------WebKitFormBoundary7MA4YWxk
Content-Disposition: form-data; name="access_key"

YOUR_ACCESS_KEY
------WebKitFormBoundary7MA4YWxk
Content-Disposition: form-data; name="resume"; filename="cv.pdf"
Content-Type: application/pdf

%PDF-1.7 ... (the file's raw bytes)
------WebKitFormBoundary7MA4YWxk--

multiple

Upload multiple files

Add the multiple attribute and people can pick several files in one go (Shift- or Ctrl/Cmd-click in the picker). The browser sends each file as its own part under the same name, so a plural name like files reads well. PHP is the exception: it needs name="files[]" to collect them as an array. On paid plans, splitforms stores up to 5 files per submission.

multiple-files.html
<form action="https://splitforms.com/api/submit" method="POST"
      enctype="multipart/form-data">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">

  <label for="files">Attach up to 5 files</label>
  <input type="file" id="files" name="files" multiple>

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

File.size

Limit file size with JavaScript

HTML has no max-size attribute, so check each file's size (in bytes) when the selection changes. setCustomValidity turns the check into a normal validation error: the browser shows your message and blocks the submit until the user picks a smaller file. splitforms accepts files up to 5 MB each, so 5 MB is a sensible cap.

Like accept, a client-side check is a UI hint, not a security control. Always validate type and size again on the server.

limit-size.html
<input type="file" id="upload" name="files" multiple>

<script>
  const MAX_BYTES = 5 * 1024 * 1024; // 5 MB
  const input = document.getElementById("upload");

  input.addEventListener("change", () => {
    input.setCustomValidity("");
    for (let i = 0; i < input.files.length; i++) {
      if (input.files[i].size > MAX_BYTES) {
        input.setCustomValidity(input.files[i].name + " is over 5 MB.");
        break;
      }
    }
    input.reportValidity();
  });
</script>

FormData

Upload with fetch and FormData

new FormData(form) collects every named field, files included. Pass it straight to fetch as the body and don't set Content-Type yourself: the browser writes the multipart header with its boundary, and a hand-written one breaks the upload. fetch has no upload progress event, so use XMLHttpRequest and upload.onprogress when you need a progress bar.

fetch-upload.html
<form id="upload-form" action="https://splitforms.com/api/submit"
      method="POST" enctype="multipart/form-data">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
  <input type="file" name="files" multiple>
  <button type="submit">Upload</button>
</form>

<script>
  const form = document.getElementById("upload-form");

  form.addEventListener("submit", async (event) => {
    event.preventDefault();
    const body = new FormData(form); // text fields + files

    // No Content-Type header: the browser adds the multipart boundary.
    const res = await fetch(form.action, { method: "POST", body });
    const data = await res.json();
    if (!res.ok) return alert(data.message);
    if (data.note) console.warn(data.note); // e.g. files that weren't stored
    form.reset();
  });
</script>

URL.createObjectURL

Show an image preview

URL.createObjectURL gives you a temporary blob: URL for the chosen file, so the page can show it without uploading anything. Revoke the URL once the image has loaded to free the memory. accept="image/*" steers the picker to images, but still check the type on the server.

image-preview.html
<input type="file" id="photo" name="photo" accept="image/*">
<img id="preview" alt="Selected image preview" width="240" hidden>

<script>
  const photo = document.getElementById("photo");
  const preview = document.getElementById("preview");

  photo.addEventListener("change", () => {
    const file = photo.files[0];
    if (!file) return;
    const url = URL.createObjectURL(file);
    preview.onload = () => URL.revokeObjectURL(url); // free the memory
    preview.src = url;
    preview.hidden = false;
  });
</script>

::file-selector-button

Style the Choose file button

The ::file-selector-button pseudo-element styles the button inside the file input and works in all current browsers. The "No file chosen" text is drawn by the browser and can't be changed. For full control, visually hide the input and style its <label> as the button instead. Either way, keep a visible keyboard focus style.

file-input.css
input[type="file"]::file-selector-button {
  margin-right: 12px;
  padding: 8px 14px;
  border: 0;
  border-radius: 8px;
  background: #c93d00;
  color: #fff;
  font: inherit;
  cursor: pointer;
}

input[type="file"]::file-selector-button:hover {
  background: #9f3000;
}

input[type="file"]:focus-visible {
  outline: 2px solid #c93d00;
  outline-offset: 2px;
}

capture

Use capture for phone cameras

On phones, capture skips the file picker and opens the camera: environment for the rear camera, user for the front one. Desktop browsers ignore it and show the normal picker. Leave capture off when people should also be able to choose a photo they already took.

camera-capture.html
<!-- Rear camera: documents, receipts, damage photos -->
<input type="file" name="receipt" accept="image/*" capture="environment">

<!-- Front camera: a selfie -->
<input type="file" name="selfie" accept="image/*" capture="user">

Copy-paste ready

The HTML File Input (Upload) example below is a complete, working form — paste it into any HTML page and it submits, no edits to the markup required.

No backend to build

Your html file input (upload) form posts to one endpoint. No server, PHP, or API route — accepted submissions are saved to your dashboard before owner notification email is attempted; delivery time and inbox placement depend on provider and recipient settings.

Where do uploaded files go?

File uploads need a paid splitforms plan with Storage connected (up to 5 files of 5 MB per submission); on the Free plan, text fields are saved and files are dropped.

How it works

How to add HTML File Input (Upload) to any page.

Copy the html file input (upload) example, drop in your free access key, and paste it into any page — submissions start landing in your dashboard.

Copy the code

Copy the HTML File Input (Upload) example below.

Add your access key

Replace YOUR_ACCESS_KEY with a free splitforms key from the dashboard.

Paste into your site

Drop the form into any HTML page. Submissions arrive in your dashboard.

How SplitForms works

What happens after someone submits your form?

splitforms receives the submission, filters spam, saves it to your dashboard and emails you — then, on paid plans, sends it on to your webhooks and integrations. No backend infrastructure to build.

A SplitForms contact form submission launching straight to your inbox

Add your endpoint

Point your form to your unique SplitForms endpoint. That's it.

HTML form pointing at a SplitForms submit endpoint

Receive submissions

We instantly capture and organize every submission in your inbox.

Submissions inbox with searchable leads and status pills

Route anywhere

Send data to email, spreadsheets, CRMs, webhooks, and 7,000+ apps.

Generic integration tiles for email, sheets, chat, CRM, automate, and webhook

No credit card required. Set up in under 60 seconds.

Connect & automate

Where can submissions go besides email?

On paid plans, submissions can also go to Slack, Discord, Google Sheets, Notion, Airtable, Zapier or any signed webhook — and splitforms works on the platforms you build on, from WordPress and Shopify to Next.js. With Stripe connected, your forms can take payments too.

Connect your SplitForms form to Slack, Google Sheets, Mailchimp, Zapier and more
NewTake payments on your formsDeposits, bookings, order forms — submitters check out through Stripe and the money goes straight to your Stripe account. Included on every paid plan.See plans →

Setup guides for the stacks you use

Questions

HTML File Input (Upload) questions, answered.

View all FAQs
How do I upload a file in HTML?

Put <input type="file" name="file"> inside a <form> with method="post" and enctype="multipart/form-data". The browser sends the file in the request body to the form's action URL, where a server or form backend has to receive and store it.

What is the correct enctype for file upload?

multipart/form-data. It encodes the form fields and the file contents as separate parts of the request body, which is required for the file to arrive intact. application/x-www-form-urlencoded (the default) drops the file data.

How do I upload multiple files with one HTML input?

Add the multiple attribute and a name such as files; the browser sends each selected file as its own part under that name. Read input.files to list the chosen file names before submitting.

How do I restrict file types in an upload?

Add an accept attribute, e.g. accept=".pdf" or accept="image/png, image/jpeg". This filters what the file picker shows, but it is a UX hint only — re-validate the type and size on the server.

How do I limit the file size of an HTML file upload?

HTML has no max-size attribute. Check input.files[i].size in a change handler and block the submit if it's too big, and always enforce the limit on the server too. splitforms accepts up to 5 files of 5 MB each per submission.

How do I upload files with JavaScript without reloading the page?

Build new FormData(form) and send it with fetch(form.action, { method: "POST", body }). Don't set Content-Type yourself, because the browser adds the multipart boundary. For a progress bar, use XMLHttpRequest and its upload.onprogress event.

How do I show an image preview before uploading?

On the input's change event, create a URL with URL.createObjectURL(file), set it as an img src, and call URL.revokeObjectURL when you're done. Add accept="image/*" to steer users to images.

How do I style the Choose file button?

Use the ::file-selector-button pseudo-element, supported in all modern browsers, or visually hide the input and style its label. Keep a visible keyboard focus style either way.

Why is my file input not uploading?

The usual causes: the <form> is missing enctype="multipart/form-data", the method is get instead of post, the input has no name, or the backend at the action URL doesn't accept multipart uploads. Check the request in the Network tab. With splitforms, files are only stored on a paid plan with Storage connected; on Free, the text fields arrive and the files are dropped.

Where do uploaded files go with splitforms, and is it free?

Uploads need a paid plan with the Storage integration connected (Pro at $5/month or Annual at $49/year): up to 5 files of 5 MB each per submission, with links in the notification email and dashboard. On the Free plan, text fields are saved but files are dropped.

Simple pricing

How much does splitforms cost?

Free gives you 500 submissions for $0 — no card, no time limit. Pro is $5/month for 5,000 submissions a month, and Annual is $49/year for 15,000 a month with every Pro feature.

Free

$0— 500 submissions
 

No card, no time limit

Best for testing

For side projects and indie devs.

  • 500 free submissions
  • Unlimited forms
  • Email notifications included
  • Smart spam filtering
  • Submissions dashboard
  • Redirect after submit
  • MCP server included

Annual

$49/ year
About $4.08/mo, billed yearly
Best value

Every Pro feature with triple the volume — $0.27 per 1,000 at full use.

  • 15,000 submissions / mo
  • Unlimited forms
  • Everything in Pro
  • File uploads with 5 GB storage
  • 3 team seats
  • Priority email support

No credit card required on Free • Cancel anytime • Full pricing and FAQ