Text and choice controls
File inputs
Accept files with the native file control, restrict suggested formats, and understand that server-side checks remain essential.
type="file" lets a visitor pick a file from their device. The browser opens the system file picker and holds the file until the form is submitted.
Here is a profile photo upload:
<form action="/profile/photo" method="post" enctype="multipart/form-data">
<label for="photo">Profile photo</label>
<input id="photo" name="photo" type="file" accept="image/png,image/jpeg">
<button type="submit">Upload photo</button>
</form>
Three things to notice.
The name identifies the file part in the request, just like a text field. accept tells the picker which formats to suggest. And enctype="multipart/form-data" changes how the whole form is encoded.
Why multipart matters
The default form encoding can only carry text. Forget the enctype and the request still goes out, but the file part contains only the filename, photo=avatar.png. No bytes. The server sees a string and no upload.
This is the first thing I check when an upload endpoint receives “nothing”. multipart/form-data splits the body into parts, one per field, and each part can hold binary data with its own filename and media type.
accept is a suggestion
accept filters what the picker shows. It reduces mistakes, and on most systems the person can still switch to “All files” and pick anything.
It is not a security boundary. A custom client can upload any bytes, with any filename, and declare any media type it likes. Treat all three as claims.
What the server has to do
The server owns the real checks:
- set a request-size limit before parsing, so a huge upload can’t fill memory or disk
- confirm a file was actually sent
- inspect the real bytes to find the type, instead of trusting the declared one
- decide whether that type is allowed
- generate its own storage name
That last point matters. A filename like ../../etc/passwd is user input. Never use it as a path. Store the file under a name you generate, and keep the original filename as plain data if you need it.
Several files
Add multiple when the endpoint and the interface can handle more than one file:
<input id="photos" name="photos" type="file" accept="image/*" multiple>
The request then contains several parts under the same name. Every one needs the same checks. Ten small files can add up to one large request, so the size limit still applies to the total.
Filenames on screen
Don’t inject a chosen filename into the page as HTML. A filename can contain <script> like any other string. Set it as text, not as markup.
Try this on your endpoint: submit with no file, with a real PNG, and with a .txt file renamed to .png. The browser can help with the first two. Only the server can catch the third.
Lesson completed