What is XMLHttpRequestUpload?
When building modern web applications that handle file uploads, providing users with real-time progress feedback is essential for a smooth user experience. While the Fetch API has become the standard for HTTP requests in modern JavaScript, it lacks native support for tracking upload progress. XMLHttpRequestUpload remains the go-to solution when you need to monitor how much of a file has been uploaded to the server.
This interface, part of the XMLHttpRequest API family, provides event hooks specifically designed for tracking the upload phase of HTTP requests.
Key Takeaways
- XMLHttpRequestUpload provides native upload progress tracking
- Events include loadstart, progress, abort, error, load, timeout, and loadend
- Event listeners must be attached before calling send()
- Cross-origin uploads may trigger preflight requests when using upload events
- Still necessary despite Fetch API being available for modern HTTP requests
Upload Events Reference
The XMLHttpRequestUpload interface provides a comprehensive set of events that cover the entire upload lifecycle.
Progress Event
The progress event fires periodically during the upload process:
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
console.log(`Uploaded ${percentComplete.toFixed(2)}%`);
}
};
Lifecycle Events
| Event | Description |
|---|---|
loadstart | Upload has begun |
progress | Periodic progress update |
abort | Upload was aborted |
error | Upload failed due to error |
load | Upload completed successfully |
timeout | Upload timed out |
loadend | Upload finished (any outcome) |
Only one terminal event (load, error, abort, or timeout) will fire for each upload.
Inheritance Hierarchy
EventTarget
↓
XMLHttpRequestEventTarget
↓
XMLHttpRequestUpload
This inheritance means you can use standard event patterns like addEventListener() with upload objects.
Practical Implementation: File Upload with Progress
Here's a complete example of file upload with progress tracking:
function uploadFile(file) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// Attach event listeners BEFORE send()
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
updateProgressBar(percentComplete);
}
};
xhr.upload.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(`Status ${xhr.status}`));
}
};
xhr.upload.onerror = function() {
reject(new Error('Network error'));
};
xhr.timeout = 60000; // 60 second timeout
xhr.ontimeout = function() {
reject(new Error('Upload timed out'));
};
xhr.open('POST', '/api/upload');
xhr.send(file);
});
}
Using FormData for Multipart Uploads
function uploadWithMetadata(file, metadata) {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('file', file);
Object.keys(metadata).forEach(key => {
formData.append(key, metadata[key]);
});
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
updateProgressBar((event.loaded / event.total) * 100);
}
};
xhr.open('POST', '/api/upload-with-metadata');
xhr.send(formData);
}
XMLHttpRequestUpload vs Fetch API
Why Fetch Lacks Upload Progress
The Fetch API has become the standard for HTTP requests in modern JavaScript, but it deliberately omits upload progress tracking. While download progress is possible using ReadableStream, the upload phase cannot be monitored with Fetch.
When to Use Each
Use Fetch API for:
- Data retrieval and API calls
- Simple POST requests without progress needs
- Modern async/await code patterns
Use XMLHttpRequestUpload for:
- File uploads requiring progress bars
- Applications supporting older browsers
- Any upload where user feedback is important
// Fetch - no upload progress available
fetch('/api/upload', {
method: 'POST',
body: file
});
// XHR - upload progress available
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = (e) => { /* track progress */ };
xhr.open('POST', '/api/upload');
xhr.send(file);
Many applications use both APIs: Fetch for data operations and XMLHttpRequest for file uploads with progress indicators. For emerging browser request capabilities, also explore the FetchLater API, which provides background fetch capabilities for progressive web applications.
Cross-Origin Requests and CORS
When uploading to a different origin, CORS considerations apply.
Preflight Requirements
Attaching event listeners to the upload object prevents the request from being a "simple request." The browser will send a preflight OPTIONS request to verify the server permits the cross-origin upload.
Important: Event listeners must be registered before calling send(), or upload events will not be dispatched.
Credential Handling
By default, cross-origin requests do not include cookies. To send credentials:
xhr.withCredentials = true;
xhr.open('POST', 'https://api.example.com/upload');
xhr.send(formData);
The server must include Access-Control-Allow-Credentials: true for this to work.
Best Practices
Event Listener Timing
Always attach event listeners before calling send():
const xhr = new XMLHttpRequest();
// Attach listeners FIRST
xhr.upload.onloadstart = handleStart;
xhr.upload.onprogress = handleProgress;
xhr.upload.onload = handleSuccess;
xhr.upload.onerror = handleError;
// Then configure and send
xhr.open('POST', '/api/upload');
xhr.send(formData);
Handling Non-Computable Progress
When Content-Length is unavailable, show absolute bytes:
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
const percent = (event.loaded / event.total) * 100;
updateProgressBar(percent);
} else {
updateStatus(`${formatBytes(event.loaded)} uploaded`);
}
};
Timeout Configuration
// Set timeout appropriate for file size
xhr.timeout = 300000; // 5 minutes for large files
xhr.ontimeout = function() {
showTimeoutMessage();
offerRetry();
};
Abort Handling
function cancelUpload() {
if (currentXHR) {
currentXHR.abort();
currentXHR = null;
}
}
Browser Compatibility
XMLHttpRequestUpload enjoys excellent browser support across all modern browsers:
| Browser | Support |
|---|---|
| Chrome | Full |
| Firefox | Full |
| Safari | Full |
| Edge | Full |
Baseline Status: Widely available since July 2015.
Web Worker Support
XMLHttpRequestUpload is available in Web Workers, allowing background upload tracking:
// In a Web Worker
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = function(event) {
self.postMessage({ type: 'progress', loaded: event.loaded, total: event.total });
};
xhr.open('POST', '/api/upload');
xhr.send(fileData);
Note: Service Workers do not support XMLHttpRequestUpload.
Security Considerations
Server-Side Validation
Client-side progress tracking does not replace server-side security. Always validate:
- File types on the server
- File sizes before processing
- File content for malicious payloads
Limit File Sizes
Implement client-side size limits:
const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
function uploadFile(file) {
if (file.size > MAX_FILE_SIZE) {
return Promise.reject(new Error('File too large'));
}
// Proceed with upload...
}
For secure file upload implementation, consider working with our web security services to ensure proper validation and protection against common vulnerabilities.
Frequently Asked Questions
Why can't the Fetch API track upload progress?
The Fetch API specification deliberately omits upload progress tracking. While download progress is possible using ReadableStream, the upload phase cannot be monitored. XMLHttpRequestUpload remains the only native browser API for this purpose.
When should I attach XMLHttpRequest upload event listeners?
Always attach event listeners before calling send(). While the specification suggests attaching after open(), browser implementations vary. Attaching first ensures compatibility across all browsers.
What causes a preflight request during file upload?
Attaching event listeners to the upload object prevents the request from being a 'simple request' under CORS rules. This triggers a preflight OPTIONS request. For same-origin uploads, no preflight occurs.
How do I handle uploads without Content-Length?
When event.lengthComputable is false, you cannot calculate a percentage. Show absolute bytes transferred instead, such as '50MB uploaded' without a progress bar.
Is XMLHttpRequestUpload available in Web Workers?
Yes, XMLHttpRequestUpload works in Web Workers but not in Service Workers. This allows background upload tracking in worker scripts.
Conclusion
XMLHttpRequestUpload remains essential for file uploads requiring progress feedback. Despite the Fetch API's modern design, upload progress tracking keeps XHR relevant in production applications.
Key takeaways:
- Event Model: Use loadstart, progress, load, error, abort, timeout, and loadend events
- Timing: Attach listeners before send() for reliability
- CORS: Upload events may trigger preflight requests for cross-origin uploads
- Best Practices: Handle non-computable progress, set timeouts, provide abort capability
- Browser Support: Widely supported across all modern browsers since 2015
For applications needing upload progress, XMLHttpRequestUpload provides the reliable, well-supported solution that the Fetch API cannot match. If you need help implementing robust file upload features in your web application, our full-stack development team can help you build secure, performant upload solutions.