# What is XMLHttpRequest? Complete Guide to XHR in JavaScript

### Quick answers:

 | Question | Answer |
|---|---|
| What is XMLHttpRequest? | A JavaScript API for making asynchronous HTTP requests from browsers to servers. |
| Is XHR still used today? | Yes, though many developers prefer the modern Fetch API for new projects. |
| What does "asynchronous” mean? | The browser doesn't wait for the response before continuing to execute other code. |
| Can XHR only handle XML? | No, XHR can handle any data type, including JSON, HTML, text, and binary data. |
| What's the difference between XHR and AJAX? | AJAX is a technique that uses XHR (or Fetch) to create dynamic web applications. |
| Should I use XHR or Fetch? | For new projects, Fetch provides cleaner syntax. Use XHR when supporting older browsers or when you need to monitor upload progress. |
| Is XMLHttpRequest secure? | XHR itself is secure when used over HTTPS. Always encrypt sensitive data in transit. |

[ Try Postman today →](https://identity.getpostman.com/signup)

 

  ### Table of Contents

- [How XMLHttpRequest works](#how-xmlhttprequest-works)
- [Creating your first XMLHttpRequest](#creating-your-first-xmlhttprequest)
- [Making POST requests with XMLHttpRequest](#making-post-requests-with-xmlhttprequest)
- [Handling XMLHttpRequest responses](#handling-xmlhttprequest-responses)
- [Using modern event handlers](#using-modern-event-handlers)
- [Working with request headers](#working-with-request-headers)
- [Monitoring request progress](#monitoring-request-progress)
- [Handling errors and timeouts](#handling-errors-and-timeouts)
- [XMLHttpRequest vs Fetch API](#xmlhttprequest-vs-fetch-api)
- [Real-world XMLHttpRequest examples](#real-world-xmlhttprequest-examples)
- [Testing XMLHttpRequest in Postman](#testing-xmlhttprequest-in-postman)
- [Best practices for XMLHttpRequest](#best-practices-for-xmlhttprequest)
- [Common mistakes to avoid](#common-mistakes-to-avoid)
 
- [Understanding AJAX and XHR](#understanding-ajax-and-xhr)
- [Final thoughts](#final-thoughts)
 
 

XMLHttpRequest (XHR) is a fundamental JavaScript API that allows web pages to communicate with servers asynchronously, forming the basis of modern web interactivity. When you use XHR, your webpage can request data, update content dynamically, submit forms in the background, and upload files without requiring a page reload.

In this guide, you'll learn how XHR works, see step-by-step examples, and understand how it compares to newer alternatives like the Fetch API. We'll also show you how to test XMLHttpRequest calls in Postman before adding them to your code, so you can debug faster and ensure your API requests work as expected.

## How XMLHttpRequest works

Despite its name suggesting XML-only functionality, XHR can handle any data format, including JSON, HTML, plain text, and binary data. XHR is the foundation of AJAX (Asynchronous JavaScript and XML), the technique that enables modern single-page applications and interactive web experiences.

XMLHttpRequest follows a request-response model where your JavaScript code initiates a request and handles the server's response through callback functions.

### Basic XHR workflow

The typical XMLHttpRequest lifecycle involves these steps:

1. Create an XHR object using the XMLHttpRequest constructor.
2. Configure the request with the HTTP method and URL.
3. Set up event handlers to process the response.
4. Send the request to the server.
5. Process the response when it arrives.
 
 | Value | State | Description |
|---|---|---|
| 0 | UNSENT | XHR object created but open() not called |
| 1 | OPENED | open() has been called |
| 2 | HEADERS\_RECEIVED | send() called, headers and status available |
| 3 | LOADING | Downloading response body |
| 4 | DONE | Operation complete |

The `readyState` property tracks this progression, and the `onreadystatechange` event fires each time it changes.

## Creating your first XMLHttpRequest

Here's a simple example that fetches user data from an API:

 ```
// Create a new XMLHttpRequest object
const xhr = new XMLHttpRequest();

// Configure the request
xhr.open('GET', 'https://api.example.com/users/123', true);

// Set up the callback function
xhr.onreadystatechange = function() {
  // Check if the request is complete
  if (xhr.readyState === 4) {
    // Check if the request was successful
    if (xhr.status === 200) {
      const userData = JSON.parse(xhr.responseText);
      console.log('User data:', userData);
    } else {
      console.error('Request failed with status:', xhr.status);
    }
  }
};

// Send the request
xhr.send();
```

### Breaking down the example

**Creating the object**: `new XMLHttpRequest()` creates a new XHR instance that can make one request.

**Configuring with open()**: The `open()` method takes three parameters:

- HTTP method (`'GET'`, `'POST'`, `'PUT'`, etc.)
- URL endpoint
- Asynchronous flag (`true` for async, `false` for synchronous)
 
**Setting up the handler**: The `onreadystatechange` function executes every time `readyState` changes. We check for state 4 (DONE) and status 200 (OK) to confirm successful completion.

**Sending the request**: The `send()` method initiates the request. For GET requests, pass `null` or no argument. For POST requests, include the request body.

## Making POST requests with XMLHttpRequest

POST requests send data to the server for creating or updating resources:

 ```
const xhr = new XMLHttpRequest();

xhr.open('POST', 'https://api.example.com/users', true);

// Set the Content-Type header
xhr.setRequestHeader('Content-Type', 'application/json');

xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if (xhr.status === 201) {
      const newUser = JSON.parse(xhr.responseText);
      console.log('User created:', newUser);
    } else {
      console.error('Failed to create user:', xhr.status);
    }
  }
};

// Prepare the data
const userData = {
  name: 'Penny Postman',
  email: 'penny@example.com',
  role: 'Developer'
};

// Send the request with JSON data
xhr.send(JSON.stringify(userData));
```

### Important POST considerations

**Set Content-Type**

Always specify `Content-Type` to tell the server what format you're sending:

- `application/json` for JSON data
- `application/x-www-form-urlencoded` for form data
- `multipart/form-data` for file uploads
 
**Stringify JSON**

Use `JSON.stringify()` to convert JavaScript objects to JSON strings before sending.

**Check appropriate status codes**

POST requests typically return 201 (Created) for successful resource creation.

## Handling XMLHttpRequest responses

XHR provides multiple ways to access response data depending on the content type.

### Response properties

**responseText**

Returns the response as a string, useful for JSON, HTML, or plain text:

 ```
const data = JSON.parse(xhr.responseText);
```

**responseXML**

Returns the response as an XML document object when the Content-Type is `text/xml` or `application/xml`:

 ```
const xmlDoc = xhr.responseXML;
const items = xmlDoc.getElementsByTagName('item');
```

**response**

Returns the response in the format specified by `responseType`:

 ```
xhr.responseType = 'json';
xhr.onload = function() {
  console.log(xhr.response); // Already parsed as JSON
};
```

**status**

[HTTP status code](https://blog.postman.com/what-are-http-status-codes/) (200, 404, 500, etc.)

**statusText**

Status message (for example: "OK", "Not Found")

### Setting response type

You can specify how the browser should parse the response:

 ```
xhr.responseType = 'json';  // Automatically parse JSON
xhr.responseType = 'blob';  // For binary data like images
xhr.responseType = 'arraybuffer';  // For raw binary data
xhr.responseType = 'document';  // For HTML/XML documents
xhr.responseType = 'text';  // Plain text (default)
```

## Using modern event handlers

While `onreadystatechange` works, modern browsers support cleaner event-based approaches:

 ```
const xhr = new XMLHttpRequest();

xhr.open('GET', 'https://api.example.com/data', true);

// Clean success handler
xhr.onload = function() {
  if (xhr.status >= 200 && xhr.status < 300) {
    console.log('Success:', xhr.responseText);
  } else {
    console.error('Request succeeded but returned error status:', xhr.status);
  }
};

// Network error handler
xhr.onerror = function() {
  console.error('Network request failed');
};

// Timeout handler
xhr.ontimeout = function() {
  console.error('Request timed out');
};

// Set a timeout (in milliseconds)
xhr.timeout = 5000;

xhr.send();
```

### Event handler advantages

**onload**

Fires only when the request completes successfully (readyState 4), eliminating the need to check `readyState`.

**onerror**

Handles network-level failures like lost connections, not HTTP error status codes.

**ontimeout**

Triggers when requests exceed the specified timeout duration.

**onprogress**

Monitors download progress, useful for large files or slow connections.

## Working with request headers

Headers provide metadata about requests and responses.

### Setting request headers

Use `setRequestHeader()` after `open()` but before `send()`:

 ```
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/data', true);

// Authentication
xhr.setRequestHeader('Authorization', 'Bearer your_token_here');

// Content type
xhr.setRequestHeader('Content-Type', 'application/json');

// Custom headers
xhr.setRequestHeader('X-Custom-Header', 'CustomValue');

xhr.send(JSON.stringify({ data: 'example' }));
```

### Reading response headers

Access response headers using `getResponseHeader()` or `getAllResponseHeaders()`:

 ```
xhr.onload = function() {
  // Get a specific header
  const contentType = xhr.getResponseHeader('Content-Type');
  console.log('Content-Type:', contentType);
  
  // Get all headers as a string
  const allHeaders = xhr.getAllResponseHeaders();
  console.log('All headers:', allHeaders);
};
```

## Monitoring request progress

Track upload and download progress for a better user experience:

 ```
const xhr = new XMLHttpRequest();

xhr.open('POST', 'https://api.example.com/upload', true);

// Monitor upload progress
xhr.upload.onprogress = function(event) {
  if (event.lengthComputable) {
    const percentComplete = (event.loaded / event.total) * 100;
    console.log(`Upload: ${percentComplete.toFixed(2)}%`);
  }
};

// Monitor download progress
xhr.onprogress = function(event) {
  if (event.lengthComputable) {
    const percentComplete = (event.loaded / event.total) * 100;
    console.log(`Download: ${percentComplete.toFixed(2)}%`);
  }
};

xhr.send(fileData);
```

## Handling errors and timeouts

Robust error handling prevents silent failures and improves user experience:

 ```
const xhr = new XMLHttpRequest();

xhr.open('GET', 'https://api.example.com/data', true);

xhr.timeout = 10000; // 10 seconds

xhr.onload = function() {
  if (xhr.status >= 200 && xhr.status < 300) {
    console.log('Success:', xhr.responseText);
  } else if (xhr.status === 404) {
    console.error('Resource not found');
  } else if (xhr.status === 500) {
    console.error('Server error');
  } else {
    console.error('Request failed with status:', xhr.status);
  }
};

xhr.onerror = function() {
  console.error('Network error occurred');
};

xhr.ontimeout = function() {
  console.error('Request timed out after 10 seconds');
};

xhr.onabort = function() {
  console.error('Request was aborted');
};

xhr.send();
```

## XMLHttpRequest vs Fetch API

Modern browsers support the Fetch API, which provides a cleaner, promise-based alternative to XHR.

### Comparing the approaches

**XMLHttpRequest example**:

 ```
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/users', true);

xhr.onload = function() {
  if (xhr.status === 200) {
    const data = JSON.parse(xhr.responseText);
    console.log(data);
  }
};

xhr.send();
```

**Fetch API example**:

 ```
fetch('https://api.example.com/users')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));
```

### When to use XHR vs Fetch

**Use XMLHttpRequest when**:

- Supporting older browsers (IE10 and below)
- Monitoring upload/download progress precisely
- Working with legacy codebases
- Aborting requests mid-flight with fine control
 
**Use Fetch when**:

- Building new applications
- Using modern async/await syntax
- Working with Service Workers
- Preferring promise-based code
 
## Real-world XMLHttpRequest examples

### Form submission without page reload

 ```
function submitForm(event) {
  event.preventDefault();
  
  const xhr = new XMLHttpRequest();
  const form = event.target;
  const formData = new FormData(form);
  
  xhr.open('POST', '/api/contact', true);
  
  xhr.onload = function() {
    if (xhr.status === 200) {
      document.getElementById('message').textContent = 'Form submitted successfully!';
      form.reset();
    } else {
      document.getElementById('message').textContent = 'Submission failed. Please try again.';
    }
  };
  
  xhr.send(formData);
}

document.getElementById('contactForm').addEventListener('submit', submitForm);
```

### Loading content dynamically

 ```
function loadArticle(articleId) {
  const xhr = new XMLHttpRequest();
  
  xhr.open('GET', `/api/articles/${articleId}`, true);
  xhr.responseType = 'json';
  
  xhr.onload = function() {
    if (xhr.status === 200) {
      const article = xhr.response;
      document.getElementById('article-title').textContent = article.title;
      document.getElementById('article-content').innerHTML = article.content;
    }
  };
  
  xhr.send();
}
```

### File upload with progress

 ```
function uploadFile(file) {
  const xhr = new XMLHttpRequest();
  const formData = new FormData();
  
  formData.append('file', file);
  
  xhr.open('POST', '/api/upload', true);
  
  xhr.upload.onprogress = function(event) {
    if (event.lengthComputable) {
      const percent = (event.loaded / event.total) * 100;
      document.getElementById('progress').value = percent;
    }
  };
  
  xhr.onload = function() {
    if (xhr.status === 200) {
      console.log('Upload complete:', xhr.responseText);
    }
  };
  
  xhr.send(formData);
}
```

## Testing XMLHttpRequest in Postman

In Postman, you can test [API endpoints](https://blog.postman.com/what-is-an-api-endpoint/) before implementing XHR requests in your code.

To test endpoints that you'll call with XHR:

1. Create a new request.
2. Select the HTTP method.
3. Enter the endpoint URL.
4. Add necessary headers, like `Content-Type` and `Authorization` .
5. For POST/PUT requests, add the request body.
6. Click **Send**.
 
Once you've verified the endpoint works correctly, use Postman's code generation feature to create XHR code snippets:

1. Click the code icon (`</>`) in the top right
2. Select "**JavaScript - XHR**" from the dropdown
3. Copy the generated code into your project
 
This workflow helps you validate API behavior before writing code and provides a starting template for your XHR implementation.

## Best practices for XMLHttpRequest

**Always handle errors**

Don't assume requests will succeed. Implement `onerror`, `ontimeout`, and status code checking.

**Set appropriate timeouts**

Prevent indefinite hanging with reasonable timeout values based on expected response times.

**Use HTTPS**

Always make requests over HTTPS to encrypt data in transit and protect sensitive information.

**Handle different response types**

Set `responseType` appropriately rather than always parsing `responseText`.

**Implement retry logic**

For critical operations, implement exponential backoff for failed requests:

 ```
function requestWithRetry(url, maxRetries = 3, delay = 1000) {
  let retries = 0;
  
  function makeRequest() {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    
    xhr.onload = function() {
      if (xhr.status === 200) {
        console.log('Success:', xhr.responseText);
      } else if (retries < maxRetries) {
        retries++;
        setTimeout(makeRequest, delay * retries);
      } else {
        console.error('Max retries exceeded');
      }
    };
    
    xhr.onerror = function() {
      if (retries < maxRetries) {
        retries++;
        setTimeout(makeRequest, delay * retries);
      }
    };
    
    xhr.send();
  }
  
  makeRequest();
}
```

**Avoid synchronous requests**

Never use `false` for the async parameter in `open()`. Synchronous requests block the browser and create terrible user experiences.

**Clean up resources**

For long-running applications, abort unneeded requests to free resources:

 ```
const xhr = new XMLHttpRequest();
// ... configure request

// Abort if needed
xhr.abort();
```

## Common mistakes to avoid

### Making synchronous requests

 ```
// ❌ Bad practice - blocks the browser
xhr.open('GET', '/api/data', false);
```

Synchronous requests freeze the browser until they are completed, creating unresponsive interfaces.

### Ignoring Content-Type headers

 ```
// ❌ Incomplete POST request
xhr.open('POST', '/api/users', true);
xhr.send(JSON.stringify(userData));  // Missing Content-Type header
```

Always set `Content-Type` when sending data:

 ```
// ✅ Correct approach
xhr.setRequestHeader('Content-Type', 'application/json');
```

### Not checking ready state or status

 ```
// ❌ Unsafe - assumes success
xhr.onreadystatechange = function() {
  const data = JSON.parse(xhr.responseText);
};
```

Always verify both `readyState` and `status` before processing responses.

### Hardcoding credentials

 ```
// ❌ Security risk
xhr.setRequestHeader('Authorization', 'Bearer abc123token');
```

Use environment variables or secure storage instead of hardcoding sensitive values.

## Understanding AJAX and XHR

AJAX (Asynchronous JavaScript and XML) is a technique that uses the XMLHttpRequest object to create dynamic web applications. While AJAX originally focused on XML, modern implementations typically use JSON.

The term "AJAX" refers to an approach that uses JavaScript to make asynchronous requests and dynamically update pages, while "XMLHttpRequest" is the specific API that enables this functionality.

When people say "making an AJAX call," they usually mean creating an XMLHttpRequest (or using the Fetch API) to retrieve data asynchronously.

## Final thoughts

XMLHttpRequest remains one of the most important APIs in web history. It introduced asynchronous communication between browsers and servers, paving the way for AJAX, APIs, and the responsive experiences that developers build today.

While the Fetch API now offers a cleaner, promise-based syntax, understanding XHR helps you:

- Maintain and debug legacy JavaScript code
- Work with precise upload or download progress tracking
- Support older browsers and systems
- Deepen your understanding of how modern APIs evolved
 
Before adding XHR code to your application, you can test the same endpoints in Postman to confirm that headers, request bodies, and responses work as expected. This lets you troubleshoot faster, ensure data accuracy, and easily switch between XHR, Fetch, or other JavaScript HTTP libraries with confidence.

Whether you're maintaining existing code or creating something new, understanding XMLHttpRequest and how to validate it with Postman will help you become a stronger, more versatile developer.