How to Make AJAX Requests Manually with htmx: A Complete Guide to htmx.ajax()
Use the htmx.ajax() method to programmatically issue HTTP requests that trigger htmx’s full rendering pipeline, including events, swap strategies, and out-of-band updates, without requiring declarative hx-* attributes.
The bigskysoftware/htmx library provides a low-level JavaScript API for imperative AJAX calls that integrates seamlessly with its declarative HTML-driven workflow. When you need to trigger server requests from JavaScript logic—whether in response to complex user interactions, third-party callbacks, or dynamic state changes—the htmx.ajax() helper exposes the same request machinery that powers hx-get or hx-post attributes.
Understanding the htmx.ajax() API Architecture
In src/htmx.js, the public htmx.ajax method is bound at line 307 as a thin wrapper around the internal ajaxHelper function defined at line 4059:
htmx.ajax = ajaxHelper // <-- public entry point
This architectural choice means programmatic calls reuse htmx’s core XMLHttpRequest infrastructure, including runtime configuration for withCredentials, global headers, and timeout handling. The helper automatically routes responses through htmx’s internal rendering engine, invoking functions like swapOuterHTML, swapInnerHTML, and makeFragment that power the declarative attribute API.
Core Capabilities of Programmatic Requests
Event System Integration
Calls to htmx.ajax() trigger the complete htmx event lifecycle, including htmx:configRequest, htmx:beforeRequest, htmx:afterOnLoad, and error events. This ensures extensions and global listeners behave identically regardless of whether the request originated from an HTML attribute or JavaScript code.
Swap Mechanisms and OOB Support
The method supports all standard swap styles (innerHTML, outerHTML, beforebegin, afterend, etc.) and processes out-of-band (OOB) swaps automatically. When the server response includes elements marked with hx-swap-oob, htmx updates those targets while simultaneously handling the primary swap target specified in your JavaScript call.
Promise-Based Return Value
htmx.ajax() returns a Promise that resolves with the raw XMLHttpRequest object. This allows you to inspect status, responseText, and responseJSON properties after the DOM update completes, enabling chaining logic that depends on both the HTTP response and the resulting UI state.
Practical Implementation Examples
GET Request with Target Selection
Issue a simple GET request and inject the response into a specific DOM element using the default innerHTML swap strategy:
<div id="result">Waiting for data…</div>
<script>
// Load `/info` into #result using the default `innerHTML` swap
htmx.ajax('GET', '/info', { target: '#result' })
.then(xhr => console.log('Request finished', xhr.status));
</script>
POST Request with JSON Payload
Send structured data to your backend with custom headers and handle the 201 Created response:
<button id="saveBtn">Save</button>
<div id="msg"></div>
<script>
document.getElementById('saveBtn').addEventListener('click', () => {
const data = { name: 'Alice', age: 30 };
htmx.ajax('POST', '/users', {
target: '#msg',
swap: 'innerHTML',
headers: { 'Content-Type': 'application/json' },
values: data
}).then(xhr => {
if (xhr.status === 201) {
console.log('User created');
}
});
});
</script>
Executing Code After DOM Updates
Use the Promise resolution to run logic after htmx has completed the swap operation:
htmx.ajax('GET', '/partial')
.then(xhr => {
// The swap has already happened at this point
const newContent = document.querySelector('#partialResult');
console.log('New content length:', newContent.innerHTML.length);
});
Manual Response Handling Without Swapping
Prevent automatic DOM updates by setting swap: 'none', then manually process the raw response:
htmx.ajax('GET', '/api/data', { swap: 'none' })
.then(xhr => {
const json = JSON.parse(xhr.responseText);
console.log('Fetched data:', json);
// Manual DOM manipulation logic here
});
Summary
- htmx.ajax() exposes the internal
ajaxHelperfunction fromsrc/htmx.js(line 4059), bound to the public API at line 307, providing imperative access to htmx’s request pipeline. - The method triggers standard htmx events (
htmx:configRequest,htmx:afterOnLoad, etc.) and supports OOB swaps, history integration, and indicator elements exactly like declarative attributes. - It returns a Promise resolving to the
XMLHttpRequestobject, allowing inspection of HTTP status and response data after the swap completes. - You can disable automatic swapping with
swap: 'none'and handle responses manually while still benefiting from htmx’s request configuration and error handling.
Frequently Asked Questions
What is the difference between htmx.ajax() and the native fetch() API?
While fetch() provides generic HTTP capabilities, htmx.ajax() integrates with htmx’s entire ecosystem—it triggers the same events as hx-get or hx-post, respects global configuration like withCredentials and request headers, and processes responses through htmx’s swap engine including OOB updates. Use htmx.ajax() when you need htmx-specific functionality; use fetch() for standalone data retrieval that doesn’t require htmx’s DOM manipulation pipeline.
Can I use htmx.ajax() without specifying a target element?
Yes. If you omit the target option, htmx will not perform a default swap, but you can still process the response manually via the returned Promise or use swap: 'none' to prevent any DOM changes while maintaining access to htmx’s request configuration and event system. This is useful for fire-and-forget requests or when handling the response entirely in JavaScript logic.
Does htmx.ajax() trigger htmx events that my extensions listen for?
Absolutely. Because htmx.ajax() reuses the same internal machinery as attribute-driven requests, it fires htmx:beforeRequest, htmx:configRequest, htmx:beforeSwap, htmx:afterSwap, and error events. Extensions registered via htmx.defineExtension() will intercept and process these requests exactly as they would standard htmx interactions.
How do I handle network or HTTP errors with htmx.ajax()?
The Promise returned by htmx.ajax() resolves regardless of HTTP status codes (2xx or 4xx/5xx), so you must check xhr.status inside the .then() handler. For network-level failures (timeouts, connection errors), the Promise rejects and you can attach a .catch() handler. Alternatively, listen for the htmx:responseError or htmx:sendError events globally to centralize error handling across both programmatic and declarative requests.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →