XMLHttpRequest: Complete Introduction (2026)
Learn how XMLHttpRequest works and when to use it over Fetch
Start Building with Hypereal
Access Kling, Flux, Sora, Veo & more through a single API. Free credits to start, scale to millions.
No credit card required • 100k+ developers • Enterprise ready
XMLHttpRequest: Complete Introduction (2026)
XMLHttpRequest (XHR) is the original JavaScript API for making HTTP requests from the browser. While the Fetch API has largely replaced it for new projects, XMLHttpRequest remains relevant in 2026 for specific use cases -- particularly upload progress tracking, legacy browser support, and existing codebases.
This guide covers everything you need to know about XMLHttpRequest: basic usage, advanced features, error handling, and when to choose XHR over Fetch.
What Is XMLHttpRequest?
XMLHttpRequest is a built-in browser API that lets you send HTTP requests and receive responses without reloading the page. Despite the name, it works with any data format -- not just XML. JSON, plain text, HTML, binary data, and form data all work with XHR.
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data");
xhr.onload = function () {
console.log(JSON.parse(xhr.responseText));
};
xhr.send();
Basic GET Request
Here is the simplest possible XHR GET request:
function fetchUsers() {
const xhr = new XMLHttpRequest();
// Configure the request
xhr.open("GET", "https://api.example.com/v1/users");
// Set response type (optional, defaults to text)
xhr.responseType = "json";
// Handle successful response
xhr.onload = function () {
if (xhr.status === 200) {
console.log("Users:", xhr.response);
} else {
console.error("Error:", xhr.status, xhr.statusText);
}
};
// Handle network errors
xhr.onerror = function () {
console.error("Network error occurred");
};
// Send the request
xhr.send();
}
fetchUsers();
XHR Lifecycle
| Event | When It Fires |
|---|---|
loadstart |
Request starts sending |
progress |
Data is being received (fires periodically) |
load |
Request completed successfully |
error |
Network error (not HTTP errors like 404) |
abort |
Request was cancelled via xhr.abort() |
timeout |
Request exceeded the timeout duration |
loadend |
Request finished (after load, error, or abort) |
POST Request with JSON
function createUser(userData) {
const xhr = new XMLHttpRequest();
xhr.open("POST", "https://api.example.com/v1/users");
// Set headers
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Authorization", "Bearer your_token_here");
xhr.responseType = "json";
xhr.onload = function () {
if (xhr.status === 201) {
console.log("User created:", xhr.response);
} else {
console.error("Failed:", xhr.status, xhr.response);
}
};
xhr.onerror = function () {
console.error("Network error");
};
// Send JSON data
xhr.send(JSON.stringify(userData));
}
createUser({
name: "Jane Smith",
email: "jane@example.com",
role: "developer",
});
PUT and DELETE Requests
// PUT request to update a resource
function updateUser(id, data) {
const xhr = new XMLHttpRequest();
xhr.open("PUT", `https://api.example.com/v1/users/${id}`);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.responseType = "json";
xhr.onload = function () {
if (xhr.status === 200) {
console.log("Updated:", xhr.response);
}
};
xhr.send(JSON.stringify(data));
}
// DELETE request to remove a resource
function deleteUser(id) {
const xhr = new XMLHttpRequest();
xhr.open("DELETE", `https://api.example.com/v1/users/${id}`);
xhr.setRequestHeader("Authorization", "Bearer your_token_here");
xhr.onload = function () {
if (xhr.status === 204) {
console.log("Deleted successfully");
}
};
xhr.send();
}
Setting Request Headers
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/v1/data");
// Authentication
xhr.setRequestHeader("Authorization", "Bearer token123");
// API Key
xhr.setRequestHeader("X-API-Key", "sk_live_abc123");
// Content type (for POST/PUT)
xhr.setRequestHeader("Content-Type", "application/json");
// Custom headers
xhr.setRequestHeader("X-Request-ID", "req_abc123");
xhr.setRequestHeader("Accept-Language", "en-US");
xhr.send();
Reading Response Headers
xhr.onload = function () {
// Get a specific header
const contentType = xhr.getResponseHeader("Content-Type");
const rateLimit = xhr.getResponseHeader("X-RateLimit-Remaining");
// Get all headers as a string
const allHeaders = xhr.getAllResponseHeaders();
console.log(allHeaders);
};
Response Types
XHR supports multiple response types:
| responseType | xhr.response Type | Use Case |
|---|---|---|
"" (default) |
String | Plain text, HTML |
"text" |
String | Same as default |
"json" |
Object | API responses |
"blob" |
Blob | Images, files |
"arraybuffer" |
ArrayBuffer | Binary data |
"document" |
Document | XML/HTML parsing |
// JSON response
const xhr1 = new XMLHttpRequest();
xhr1.responseType = "json";
xhr1.onload = () => console.log(xhr1.response.name); // Direct object access
// Blob response (for downloading files)
const xhr2 = new XMLHttpRequest();
xhr2.responseType = "blob";
xhr2.onload = () => {
const url = URL.createObjectURL(xhr2.response);
const a = document.createElement("a");
a.href = url;
a.download = "file.pdf";
a.click();
URL.revokeObjectURL(url);
};
// ArrayBuffer (for binary processing)
const xhr3 = new XMLHttpRequest();
xhr3.responseType = "arraybuffer";
xhr3.onload = () => {
const data = new Uint8Array(xhr3.response);
console.log("Bytes received:", data.length);
};
Upload Progress Tracking
This is where XHR shines over the Fetch API. XHR provides granular upload and download progress events:
function uploadFile(file) {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append("file", file);
// Upload progress
xhr.upload.onprogress = function (event) {
if (event.lengthComputable) {
const percentComplete = Math.round((event.loaded / event.total) * 100);
console.log(`Upload progress: ${percentComplete}%`);
updateProgressBar(percentComplete);
}
};
xhr.upload.onload = function () {
console.log("Upload complete!");
};
// Download progress (for the response)
xhr.onprogress = function (event) {
if (event.lengthComputable) {
const percent = Math.round((event.loaded / event.total) * 100);
console.log(`Download progress: ${percent}%`);
}
};
xhr.onload = function () {
if (xhr.status === 200) {
console.log("Server response:", JSON.parse(xhr.responseText));
}
};
xhr.open("POST", "https://api.example.com/v1/upload");
xhr.send(formData);
}
// Usage with a file input
document.getElementById("fileInput").addEventListener("change", (e) => {
uploadFile(e.target.files[0]);
});
Progress Bar HTML
<input type="file" id="fileInput" />
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<span id="progressText">0%</span>
<script>
function updateProgressBar(percent) {
document.getElementById("progressFill").style.width = percent + "%";
document.getElementById("progressText").textContent = percent + "%";
}
</script>
Timeout and Abort
Setting a Timeout
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/v1/slow-endpoint");
// Set timeout to 10 seconds
xhr.timeout = 10000;
xhr.ontimeout = function () {
console.error("Request timed out after 10 seconds");
};
xhr.onload = function () {
console.log("Response received:", xhr.response);
};
xhr.send();
Aborting a Request
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/v1/large-data");
xhr.onabort = function () {
console.log("Request was cancelled");
};
xhr.send();
// Cancel after 3 seconds
setTimeout(() => {
xhr.abort();
}, 3000);
Error Handling
A complete error handling pattern:
function apiRequest(method, url, data = null) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.responseType = "json";
xhr.timeout = 15000;
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject({
status: xhr.status,
statusText: xhr.statusText,
response: xhr.response,
});
}
};
xhr.onerror = function () {
reject({ status: 0, statusText: "Network Error", response: null });
};
xhr.ontimeout = function () {
reject({ status: 0, statusText: "Timeout", response: null });
};
xhr.onabort = function () {
reject({ status: 0, statusText: "Aborted", response: null });
};
xhr.send(data ? JSON.stringify(data) : null);
});
}
// Usage with async/await
async function getUsers() {
try {
const users = await apiRequest("GET", "https://api.example.com/v1/users");
console.log(users);
} catch (error) {
if (error.status === 401) {
console.log("Please log in again");
} else if (error.status === 0) {
console.log("Network issue:", error.statusText);
} else {
console.log("API error:", error.status, error.response);
}
}
}
ReadyState Values
XHR tracks the request lifecycle through readyState:
| Value | Constant | Meaning |
|---|---|---|
| 0 | UNSENT |
open() not called yet |
| 1 | OPENED |
open() called |
| 2 | HEADERS_RECEIVED |
Response headers received |
| 3 | LOADING |
Response body loading |
| 4 | DONE |
Request complete |
xhr.onreadystatechange = function () {
switch (xhr.readyState) {
case XMLHttpRequest.OPENED:
console.log("Request opened");
break;
case XMLHttpRequest.HEADERS_RECEIVED:
console.log("Headers:", xhr.getAllResponseHeaders());
break;
case XMLHttpRequest.LOADING:
console.log("Loading data...");
break;
case XMLHttpRequest.DONE:
console.log("Request complete, status:", xhr.status);
break;
}
};
XMLHttpRequest vs Fetch API
| Feature | XMLHttpRequest | Fetch API |
|---|---|---|
| Syntax | Callback/event-based | Promise-based |
| Upload progress | Yes (xhr.upload.onprogress) |
No native support |
| Download progress | Yes (xhr.onprogress) |
Yes (via ReadableStream) |
| Abort | xhr.abort() |
AbortController |
| Timeout | Built-in (xhr.timeout) |
Manual with AbortController |
| Streaming | No | Yes |
| Service Workers | No | Yes |
| CORS | Same behavior | Same behavior |
| Request/Response objects | No | Yes (reusable) |
When to Use XMLHttpRequest
- You need upload progress tracking (file uploads with progress bars)
- You are working with legacy code that already uses XHR
- You need built-in timeout support without extra code
When to Use Fetch
- New projects -- Fetch has a cleaner API
- Streaming responses -- Fetch supports ReadableStream
- Service Workers -- Fetch is the only option
- Server-side JavaScript -- Fetch is available in Node.js 18+
Sending Form Data
// Using FormData (for file uploads and form submissions)
const formData = new FormData();
formData.append("name", "Jane");
formData.append("email", "jane@example.com");
formData.append("avatar", fileInput.files[0]);
const xhr = new XMLHttpRequest();
xhr.open("POST", "https://api.example.com/v1/users");
// Do NOT set Content-Type -- the browser sets it with the boundary
xhr.send(formData);
// Using URL-encoded data
const xhr2 = new XMLHttpRequest();
xhr2.open("POST", "https://api.example.com/v1/login");
xhr2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr2.send("username=jane&password=secret123");
Conclusion
XMLHttpRequest remains a capable and relevant API in 2026, particularly for upload progress tracking and legacy codebases. While Fetch is the default choice for new projects, understanding XHR is valuable for maintaining existing code and for scenarios where its unique features (like upload progress) are needed.
If you are building web applications that need to upload media files or display generation progress, Hypereal AI provides APIs for AI image and video generation with progress tracking -- a perfect use case for XMLHttpRequest's progress events.
Related Articles
Start Building Today
Get 35 free credits on signup. No credit card required. Generate your first image in under 5 minutes.
