On this page
Job applications need CVs. Support forms need screenshots. Quote requests need floor plans. Adding a file upload to an HTML form takes one element and one attribute — but doing it well takes a little more thought.
The basics: three things every upload form needs
<form action="https://api.formclick.io/submit"
method="POST"
enctype="multipart/form-data">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
<label for="cv">Upload your CV</label>
<input type="file" id="cv" name="cv">
<button type="submit">Send application</button>
</form>
type="file"— shows the file picker.method="POST"— files can't be sent in a URL.enctype="multipart/form-data"— without it, only the file name is sent, not the file itself. This is the most common upload bug.
Restricting file types with accept
accept filters what the file picker shows. You can use extensions, MIME types or wildcards:
<!-- Documents -->
<input type="file" name="cv" accept=".pdf,.doc,.docx">
<!-- Any image -->
<input type="file" name="photo" accept="image/*">
<!-- Specific image formats -->
<input type="file" name="logo" accept="image/png,image/svg+xml">
<!-- On phones, open the camera directly -->
<input type="file" name="receipt" accept="image/*" capture="environment">
accept is a convenience, not security — people can still choose "All files". Always validate on the server too.
Allowing multiple files
<label for="photos">Photos of the damage (up to 5)</label>
<input type="file" id="photos" name="photos" accept="image/*" multiple>
HTML can't limit how many files are picked; enforce that with a little JavaScript (below) and on the server.
Checking size and count before upload
const input = document.getElementById('photos');
const MAX_FILES = 5;
const MAX_BYTES = 5 * 1024 * 1024; // 5 MB
input.addEventListener('change', function () {
let message = '';
if (input.files.length > MAX_FILES) {
message = 'Please choose up to ' + MAX_FILES + ' files.';
}
for (const file of input.files) {
if (file.size > MAX_BYTES) {
message = file.name + ' is larger than 5 MB.';
}
}
input.setCustomValidity(message);
input.reportValidity();
});
setCustomValidity() plugs your rule into the browser's own validation, so the form won't submit until it's fixed.
Showing image previews
<input type="file" id="avatar" name="avatar" accept="image/*">
<img id="preview" alt="" width="120" hidden>
<script>
document.getElementById('avatar').addEventListener('change', function (event) {
const file = event.target.files[0];
const img = document.getElementById('preview');
if (!file) { img.hidden = true; return; }
img.src = URL.createObjectURL(file);
img.hidden = false;
});
</script>
Styling the file input
The default file button is hard to style, but the ::file-selector-button pseudo-element makes it easy:
input[type="file"] {
width: 100%;
padding: 10px;
font: inherit;
border: 1px dashed #b4b4c8;
border-radius: 12px;
background: #fafaff;
}
input[type="file"]::file-selector-button {
margin-right: 12px;
padding: 8px 14px;
font: inherit;
font-weight: 600;
color: #fff;
background: #645cec;
border: 0;
border-radius: 8px;
cursor: pointer;
}
Receiving uploaded files without a server
Handling uploads yourself means parsing multipart data, enforcing limits, scanning files, storing them somewhere safe and generating secure links. A form backend can do all of that for you.
With Formclick (Pro and Business plans), add the formclick.min.js script and your file inputs become drag-and-drop uploaders. Files up to 5 MB each are uploaded securely and linked in your submission — in the notification email and your dashboard.
<form action="https://api.formclick.io/submit" method="POST" enctype="multipart/form-data">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
<input type="hidden" name="subject" value="New job application">
<input type="checkbox" name="botcheck" style="display: none;">
<label for="name">Full name</label>
<input id="name" name="name" required>
<label for="email">Email</label>
<input id="email" name="email" type="email" required>
<label for="resume">CV (PDF or Word)</label>
<input id="resume" name="resume" type="file" accept=".pdf,.doc,.docx" required>
<label for="portfolio">Portfolio samples</label>
<input id="portfolio" name="portfolio" type="file" accept="image/*,.pdf" multiple>
<button type="submit">Apply</button>
</form>
<script src="https://cdn.formclick.io/formclick.min.js"></script>
File upload security checklist
If you ever handle uploads on your own server, follow these rules:
- Validate type on the server by inspecting file contents, not just the extension or the browser's MIME type.
- Enforce a size limit at the web server and application level.
- Rename files with random names — never trust the original filename.
- Store files outside the web root or in object storage, and serve them through signed, expiring links.
- Never execute uploaded files and set safe
Content-TypeandContent-Dispositionheaders when serving them. - Scan for malware if files are shared with other people.
UX tips
- Say what's allowed next to the field: "PDF or Word, up to 5 MB".
- Show selected file names so people know it worked.
- Make uploads optional where you can — they're the field people most often abandon on mobile.
- Show progress for large files; a button stuck on "Sending…" looks broken.
Need a starting point? See the job application example in HTML Forms for Beginners: Practical Examples or browse our HTML form templates.



