When you upload a file, the website does not get access to your folders. The browser exposes only the file you selected, wraps it in a web request, and sends the bytes to the server over an encrypted connection.
The process feels like one action because the browser hides several steps: file selection, metadata reading, request packaging, network transfer, progress tracking, and server response handling.
File selection grants narrow access
A page can ask for a file with an <input type="file">, a drag-and-drop area, or a paste target. In each case, the browser waits for a user action before exposing file data.
The selected item becomes a File object. That object can include:
name, such asphoto.jpgsize, measured in bytestype, such asimage/jpeglastModified, when available- the file’s bytes, readable through
Blobmethods
The page does not receive a path it can browse. It cannot list sibling files or open a directory unless you explicitly use a directory-selection feature.
The browser builds the request body
Most form uploads use FormData. It can hold text fields and files in the same request.
const form = new FormData();
form.append("title", "Homepage hero");
form.append("file", fileInput.files[0]);
await fetch("/upload", {
method: "POST",
body: form
});
For a normal HTML form, the browser sends that body as multipart/form-data. Multipart means the request is divided into sections, with a boundary string separating the fields.
A simplified request looks like this:
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----boundary
------boundary
Content-Disposition: form-data; name="title"
Homepage hero
------boundary
Content-Disposition: form-data; name="file"; filename="photo.jpg"
Content-Type: image/jpeg
(binary file bytes)
------boundary--
The boundary matters because the server needs to know where each field starts and ends.
Upload bytes move over HTTPS
After the request is prepared, the browser sends it over the connection to the server. On the public web, that means HTTPS: HTTP carried inside TLS encryption.
TLS protects the bytes while they travel between browser and server. It does not decide what the server does with the file after receiving it. Storage, scanning, resizing, and deletion are server-side policy questions.
Large uploads may be streamed rather than buffered all at once. Some apps also split files into chunks so a failed network connection can resume from the last completed part. That chunking logic belongs to the app or upload service, not to the basic file input itself.
Progress bars count bytes
An upload progress bar is based on bytes sent compared with total bytes. The browser knows the selected file size, so it can report how much of the request body has moved.
Older upload code uses XMLHttpRequest because it exposes upload progress events directly:
const xhr = new XMLHttpRequest();
xhr.open("POST", "/upload");
xhr.upload.onprogress = event => {
if (!event.lengthComputable) return;
const percent = (event.loaded / event.total) * 100;
console.log(percent.toFixed(1));
};
xhr.send(form);
The number is a transfer signal, not a guarantee that the server has finished processing the file. A video can reach 100% uploaded while the server is still transcoding it.
Preview is not the same as upload
Many sites show a thumbnail before you click submit. That preview can happen entirely on your device.
The browser can create a temporary object URL or read the file with file APIs, then render the preview without sending anything:
const url = URL.createObjectURL(file);
previewImage.src = url;
A local preview means the browser has read the selected file. It does not mean the file has left your computer. The network request happens only when the app sends it.
The server receives and validates
On the server, upload handling can include:
- Checking the request size against a limit.
- Parsing the multipart sections.
- Validating the file type and extension.
- Scanning or transforming the file if needed.
- Storing the result or rejecting the upload.
- Returning a response the browser can show.
The browser cannot verify all of that from the outside. It can show the response status, such as 200, 413, or 415, but the server decides what those responses mean in that app.
Upload limits and failure points
Uploads fail for ordinary reasons:
- The file is larger than the server allows.
- The connection drops before the request finishes.
- The server rejects the file type.
- The browser tab runs out of memory while preparing a large preview.
- A mobile device sleeps or switches networks during transfer.
A good upload UI separates transfer progress from processing status. Uploading, processing, and complete are different states.
Local processing can avoid upload
Some file tasks do not need a server. Image resizing, conversion, compression, metadata inspection, text counting, and many format checks can run in the browser and produce a download.
For example, the Image Compressor reads selected images, compresses them with browser APIs, and provides a download for the result. That uses the same file-selection model as an upload, but it skips the network request.
Use an upload when a server needs the file. Use local processing when the result can be produced on the device.
Watch an upload in DevTools
Open DevTools, switch to the Network tab, then submit a form with a small test file. Select the request and inspect:
- Request headers, including
Content-Type. - Payload or form data, depending on the browser.
- Timing, including request upload and server response.
- Status code and response body.
That view shows the boundary between browser work and server work. The browser packages and sends the file. The server decides what happens next.





