What is XMLHttpRequest? Complete Guide to XHR in JavaScript

What is XMLHttpRequest? Complete Guide to XHR in JavaScript

User Avatar

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 →

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: '[email protected]',
  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 (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 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.

Tags:

What do you think about this topic? Tell us in a comment below.

Comment

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.