File Uploading
Uploading with AJAX uses a file input, a FormData body, and a POST request. The browser handles multipart encoding, and progress events report how much has been sent.
Markup
<form id="upload">
<input type="file" name="document" accept=".pdf,.docx">
<button type="submit">Upload</button>
<progress id="progress" value="0" max="100"></progress>
</form>
Fetch upload
const form = document.querySelector('#upload');
const progress = document.querySelector('#progress');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const data = new FormData(form);
const response = await fetch('/uploads', {
method: 'POST',
body: data,
headers: { Accept: 'application/json' },
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.status}`);
}
const result = await response.json();
console.log(result.url);
});
Do not set Content-Type for FormData. The browser must add the multipart boundary, and overriding the header makes the request unparseable.
Upload progress with XMLHttpRequest
The Fetch API still lacks broadly supported upload progress events. Use XMLHttpRequest when a progress bar is required.
const xhr = new XMLHttpRequest();
xhr.open('POST', '/uploads');
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
progress.value = Math.round((event.loaded / event.total) * 100);
}
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
console.log(JSON.parse(xhr.responseText));
} else {
console.error('Upload failed', xhr.status);
}
};
xhr.send(new FormData(form));
Multiple files
const input = document.querySelector('input[type="file"]');
for (const file of input.files) {
data.append('documents[]', file);
}
Validate before sending
- Check the file type using the
typeproperty, but validate content on the server. - Enforce a maximum size and a maximum count.
- Reject empty files and unexpected extensions.
- Strip or ignore client-supplied filenames when building server paths.
Server expectations
The server must enforce its own limits, store files outside the executable web root when possible, generate safe names, and return a stable URL or identifier. Uploads are among the most abused endpoints, so authentication, authorization, and rate limiting are mandatory.