HTML Input Type File
Profile photos, CVs, invoices, screenshots, homework: if your site needs to receive a file from the visitor, type="file" is how it starts. Click the field, pick a file from your device, and the browser sends it along with the rest of the form.
Uploads are also where forms become dangerous. A file is data your server did not create, with a name, a size, and a type that someone else chose. In this lesson, you will learn how to build the field, how to tell the browser which files you want, how to read and preview the choice in JavaScript, and, most important, the rules your server must follow to stay safe.
What You Will Learnβ
- How to build a file input, and what the form needs for uploads to work
- The
accept,multiple,capture, andrequiredattributes - Reading file names, sizes, and types in JavaScript
- Previewing images, and checking size before upload
- Styling the "Choose file" button
- Accessibility, safe server handling, and common mistakes
The Basicsβ
- HTML Code
- Browser Output
<form action="/upload" method="post" enctype="multipart/form-data">
<label for="avatar">Profile photo</label>
<input type="file" id="avatar" name="avatar" />
<button type="submit">Upload</button>
</form>
The field shows a "Choose file" button and the text "No file chosen." Clicking it opens the device's own file picker (and on phones, often a choice between the photo library, camera, and files). After choosing, the file's name appears next to the button.
The exact words and look come from the browser and the visitor's language. On many systems, you can also drag a file onto the field.
The Form Needs Two Settingsβ
For files to actually reach your server, the form (not the input) must have these:
| Setting | Why |
|---|---|
method="post" | Files are far too big for the address bar, so get cannot carry them |
enctype="multipart/form-data" | Tells the browser to send the data in a special "multipart" format that can carry file contents |
<form action="/upload" method="post" enctype="multipart/form-data">
If you forget enctype="multipart/form-data", the form looks fine, but the server receives only the file name as text, and not the file itself. When uploads "do not work," check this first. You will learn the details in Multipart Form Data.
What Is Sentβ
For a file input, the browser sends the file's name, its type, and its contents, as one part of the multipart message. If no file was chosen, it sends an empty part with an empty name.
Unlike other inputs, you cannot see the value in the address bar, and the field has no readable value you can use. If you read input.value, you get a fake path like C:\fakepath\photo.jpg, which exists only to hide the visitor's real folders. Use input.files instead (see below).
Attributes for File Inputsβ
| Attribute | What it does | Example |
|---|---|---|
name | The key your server reads the file under | name="avatar" |
id | Connects the label | id="avatar" |
accept | Tells the browser which file types to offer | accept="image/*" |
multiple | Allows choosing more than one file | multiple |
required | A file must be chosen | required |
capture | On phones, open the camera or microphone directly | capture="environment" |
webkitdirectory | Choose a whole folder (non-standard, but widely supported) | webkitdirectory |
disabled | Switches the field off, and it is not sent | disabled |
autofocus | Focus on load | autofocus |
Attributes that do not apply: value (you cannot pre-fill a file), readonly, placeholder, min, max, step, pattern, maxlength, and size.
The accept Attributeβ
accept narrows what the picker shows. It takes a comma-separated list of:
- Extensions that start with a dot:
.pdf,.docx - Exact MIME types:
application/pdf,image/png - Wildcard MIME types:
image/*,audio/*,video/*
<!-- Any image -->
<input type="file" name="photo" accept="image/*" />
<!-- Only PDFs -->
<input type="file" name="cv" accept=".pdf,application/pdf" />
<!-- Specific image types -->
<input type="file" name="logo" accept="image/png, image/jpeg, image/webp" />
What accept does | What it does not do |
|---|---|
| Filters the file picker, so people mostly see the right kinds | Stop anyone from sending other files. Many pickers let you switch to "All files," and a script can send anything |
| Helps phones offer the right apps | Check what is inside the file |
accept is a convenience, not securityIt is a hint to the browser. A visitor can rename a script to photo.jpg, or bypass your page entirely. Your server must check every upload, as described later.
Choosing Several Filesβ
Add multiple, and the visitor can pick more than one file (with Ctrl or Shift on a computer, or by tapping several on a phone).
<label for="gallery">Photos for the album</label>
<input type="file" id="gallery" name="photos" accept="image/*" multiple />
On the server, you receive a list of files under the same name. The field's text then says something like "3 files."
Try It: Read, Check, and Previewβ
The browser gives you the chosen files as input.files, a list of File objects. Each has:
| Property | What it gives |
|---|---|
file.name | The file name, such as photo.jpg |
file.size | The size in bytes |
file.type | The MIME type the browser guessed, such as image/jpeg (it can be empty) |
file.lastModified | When the file was last changed (milliseconds since 1970) |
Choose a few files in the demo, and see the details and a thumbnail for images.
- HTML Code
- Browser Output
<label for="files">Choose images or documents (2 MB each at most)</label>
<input type="file" id="files" name="uploads" multiple accept="image/*,.pdf" />
<ul id="list" role="status"></ul>
const input = document.getElementById("files");
const list = document.getElementById("list");
const MAX_BYTES = 2 * 1024 * 1024;
let urls = [];
function formatSize(bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
return (bytes / (1024 * 1024)).toFixed(2) + " MB";
}
input.addEventListener("change", () => {
// Free memory from earlier previews
urls.forEach((url) => URL.revokeObjectURL(url));
urls = [];
list.innerHTML = "";
for (const file of input.files) {
const item = document.createElement("li");
if (file.type.startsWith("image/")) {
const img = document.createElement("img");
const url = URL.createObjectURL(file);
urls.push(url);
img.src = url;
img.alt = "";
img.width = 48;
img.height = 48;
item.append(img, " ");
}
const text = file.name + " (" + (file.type || "unknown type") + ", " + formatSize(file.size) + ")";
item.append(text);
if (file.size > MAX_BYTES) {
item.append(" Too large: the limit is 2 MB.");
}
list.append(item);
}
});
Things to notice:
URL.createObjectURL(file)makes a temporary address for the file in the visitor's own browser, so you can show a preview without uploading anything. CallURL.revokeObjectURL(url)afterwards to free memory.- The preview image has an empty
alt=""because the file name next to it already describes it. - Nothing has left the visitor's device. Files are only sent when the form is submitted.
- Reading
file.nameas text (as above) is safe. If you ever put a file name into HTML as markup, escape it first, because file names can contain<and quotes.
Stopping Files That Are Too Bigβ
You can check the size before upload, and set a custom message with setCustomValidity, so the browser's own error bubble appears on submit:
const input = document.getElementById("files");
const MAX_BYTES = 2 * 1024 * 1024;
input.addEventListener("change", () => {
const tooBig = [...input.files].some((file) => file.size > MAX_BYTES);
input.setCustomValidity(tooBig ? "Each file must be 2 MB or smaller." : "");
});
This saves visitors a long upload that would fail at the end. But it is a courtesy only: your server must enforce the limit again.
Cameras and Microphones with captureβ
On phones and tablets, capture asks the browser to open the camera (or microphone) directly, instead of showing the file picker.
<!-- Rear camera, for photographing a document -->
<input type="file" accept="image/*" capture="environment" />
<!-- Front camera, for a selfie -->
<input type="file" accept="image/*" capture="user" />
| Value | Meaning |
|---|---|
environment | The rear (outward-facing) camera |
user | The front (selfie) camera |
capture works together with accept (image/*, video/*, or audio/*). Desktop browsers usually ignore it, and show the normal file picker. Because behaviour varies, always test on real devices, and let visitors choose an existing file if you can.
Styling the "Choose File" Buttonβ
The default look is plain, and you cannot change the text it shows. But you can style the button part with the ::file-selector-button pseudo-element:
input[type="file"] {
font: inherit;
max-width: 100%;
}
input[type="file"]::file-selector-button {
padding: 0.6rem 1rem;
margin-right: 0.75rem;
border: 1px solid #2563eb;
border-radius: 6px;
background: #2563eb;
color: #fff;
font: inherit;
cursor: pointer;
}
input[type="file"]::file-selector-button:hover {
background: #1d4ed8;
}
input[type="file"]:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 2px;
}
A Fully Custom Lookβ
Some designs hide the native input and use a button or a drop zone. To keep it accessible, keep the real input (do not use display: none), and place it visually hidden next to a styled label:
<input type="file" id="doc" name="doc" class="visually-hidden" />
<label for="doc" class="upload-button">Choose a document</label>
<span id="doc-name">No file chosen</span>
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
.upload-button {
display: inline-block;
padding: 0.6rem 1rem;
border-radius: 6px;
background: #2563eb;
color: #fff;
cursor: pointer;
}
/* Show a focus ring on the label when the hidden input has focus */
.visually-hidden:focus-visible + .upload-button {
outline: 3px solid #1e3a8a;
outline-offset: 2px;
}
Clicking the label opens the picker, and the keyboard still works with the real input. Update the file name text yourself in JavaScript, and do not forget the focus style, since the real input is invisible.
Accessibility at a Glanceβ
- Always add a visible label such as "Profile photo." Use
forandid. - State the rules in text before the visitor chooses: file types, the maximum size, and how many files are allowed. Link it with
aria-describedby: "JPG or PNG, up to 2 MB." - Announce the result: show the chosen file names in a
role="status"region, so screen reader users know their choice worked. - Give clear, specific errors: "photo.tiff is not a supported type. Use JPG, PNG, or WebP." Not just "Invalid file."
- Keep the native input (or a properly hidden one) so the keyboard (
Tab,Space,Enter) works. - Provide a text alternative for any preview image, or use
alt=""when the name already says what it is. - Offer another way when files are hard to use: for example, "or email it to us."
- Drag-and-drop zones must not be the only route. The standard button must always work.
- Tell people about progress during a long upload, and provide a way to cancel.
Safe Handling on the Serverβ
This is the most important part of the lesson. Every uploaded file is untrusted. The name, the size, the type, and the contents can all be forged. A careless upload feature is one of the classic ways sites get taken over.
| Risk | What to do |
|---|---|
| A huge file fills your disk or memory | Set a maximum size in your server, framework, and proxy. Reject early |
Wrong or fake type (a script renamed photo.jpg) | Do not trust the file name, extension, or Content-Type. Check the real content (the "magic bytes" at the start, or by decoding the image with a library). Allow only the types you expect |
A dangerous file name such as ../../etc/passwd or shell.php | Never use the visitor's file name to save the file. Generate your own random name (for example, a UUID), and keep the original only as display text, escaped |
| Files run as code | Store uploads outside the web root, or on separate storage, and serve them as downloads or static files, never executed. Turn off script execution in the upload folder |
| Malware | Scan uploads with antivirus software, especially if other people will download them |
| Images with hidden content | Re-encode images (resize and save them again), which strips scripts and metadata. This also removes location data (EXIF), protecting privacy |
| Stored XSS through SVG or HTML files | Do not serve visitor-uploaded SVG or HTML from your own domain, or serve with safe headers (Content-Disposition: attachment, X-Content-Type-Options: nosniff) |
| Abuse (many uploads, wasted storage) | Require sign-in, limit the count and rate, and clean up unused files |
| Privacy | Tell people what you do with their files, how long you keep them, and delete them on request |
Many sites send files directly from the visitor's browser to cloud storage using a short-lived, signed link, and scan them there. This keeps large, risky data away from your main server.
You will see more in Secure Form Design.
Global Audience Notesβ
- File names can use any script (such as
ΰ€«ΰ₯ΰ€ΰ₯.jpg,η §η.png, orΨ΅ΩΨ±Ψ©.jpg). Your server and storage must handle Unicode, even if you store the file under a random name. - Slow and expensive connections: mobile data costs vary a lot. State size limits clearly, resize images in the browser before upload if you can, and show progress.
- Modern phone photos are large. A single photo can be 5 to 10 MB. A 2 MB limit will surprise people, so say so before they choose, and consider automatic resizing.
- Different devices, different pickers. The "Choose file" text follows the visitor's language, but your help text does not, so translate it.
- Respect privacy laws. A photo of a person or a document is personal data in many countries.
Common Mistakes to Avoidβ
- Forgetting
enctype="multipart/form-data", so only the file name arrives. - Using
method="get". Files needpost. - Treating
acceptas security. It only filters the picker. - Trusting the file name, extension, or type on the server.
- Saving files under the visitor's file name.
- Storing uploads where the web server can run them as code.
- No size limit, or a limit only in the browser.
- Not saying the rules (types and size) before the visitor chooses.
- Reading
input.valueinstead ofinput.files. - Hiding the input with
display: none, which breaks keyboard access. - A drag-and-drop zone with no normal button.
- Forgetting
URL.revokeObjectURLafter previews, which leaks memory.
Practice Timeβ
Try these in your editor:
- Build a form with a file input,
method="post", andenctype="multipart/form-data". If you have a small server, upload a file, and see what arrives. - Remove the
enctype, upload again, and compare. - Add
accept="image/*", then try switching the picker to "All files." - Add
multiple, and choose three files. - Show each file's name, type, and size in a list, using
input.files. - Add an image preview with
URL.createObjectURL, and revoke it afterwards. - Block files over 2 MB with
setCustomValidity. - Style the button with
::file-selector-button. - Open the page on a phone, and try
capture="environment". - Bonus: build a custom upload button with a visually hidden input and a visible focus style.
Quick Recapβ
Click a question to check your answer.
1. What must a form have for file uploads to work?
method="post" and enctype="multipart/form-data". Without the enctype, only the file name is sent, and not the file itself.
2. What does the accept attribute do, and does it make uploads safe?
It filters the file picker to the types you list (extensions, MIME types, or wildcards such as image/*). It is only a hint. Visitors can bypass it, so the server must check every file.
3. How do you read the chosen files in JavaScript?
Use input.files, a list of File objects with name, size, type, and lastModified. Do not use input.value, which holds a fake path.
4. How can you preview an image before uploading it?
Make a temporary address with URL.createObjectURL(file), set it as the src of an image, and call URL.revokeObjectURL(url) when you are done.
5. What does capture="environment" do?
On phones and tablets, it opens the rear camera directly (together with accept="image/*"). capture="user" opens the front camera. Desktop browsers usually ignore it.
6. Name three rules for safe handling of uploaded files on the server.
Enforce a size limit; check the real file content and allow only expected types; never use the visitor's file name (generate your own); and store files where they cannot be run as code. Scanning and re-encoding images help too.
Key Takeawaysβ
type="file"lets visitors upload files. The form needsmethod="post"andenctype="multipart/form-data".- Use
acceptto guide the picker,multiplefor several files,requiredfor a must-have file, andcapturefor phone cameras.acceptis not security. - Read files through
input.files(name,size,type), and preview images withURL.createObjectURL, revoking the address afterwards. - Style the button with
::file-selector-button. If you hide the input, keep it accessible and show a focus style. - State the allowed types and size in text, announce the result, and give specific errors.
- Treat every upload as hostile: limit size, check real content, rename files, store them safely, scan them, and validate on the server.
Files carry whole documents. Next, learn the input for choosing a value on a scale, in Range, or return to the Forms overview.