# How htmx Constructs AJAX Requests: A Deep Dive into the Source Code

> Discover how htmx constructs AJAX requests via its four stage pipeline. Explore the source code to understand context normalization, header/payload building, XMLHttpRequest lifecycle, and response processing.

- Repository: [Big Sky Software/htmx](https://github.com/bigskysoftware/htmx)
- Tags: deep-dive
- Published: 2026-08-30

---

**htmx constructs AJAX requests through a four-stage pipeline in [`src/htmx.js`](https://github.com/bigskysoftware/htmx/blob/main/src/htmx.js) that normalizes context, builds headers and payload, manages the XMLHttpRequest lifecycle, and processes responses.**

The **bigskysoftware/htmx** library handles all network communication through a centralized request builder that bridges declarative HTML attributes and programmatic JavaScript calls. Understanding how htmx constructs AJAX requests reveals why the library can swap HTML fragments with minimal boilerplate while maintaining full control over headers, credentials, and request bodies.

## The Public API: htmx.ajax and ajaxHelper

All programmatic AJAX requests flow through `htmx.ajax`, which the library exposes as the `ajaxHelper` function at **line 307** in [`src/htmx.js`](https://github.com/bigskysoftware/htmx/blob/main/src/htmx.js).

```javascript
// The public method is attached directly to the htmx object
htmx.ajax = ajaxHelper;   // src/htmx.js line 307

```

The function signature accepts three parameters: an HTTP verb, a URL path, and an optional **context** that can be a DOM element, CSS selector, or configuration object. When invoked, `ajaxHelper` (defined at **lines 4059‑4092**) normalizes the verb via `verb.toLowerCase()` and resolves the target element using `resolveTarget`. If the context is an object, htmx extracts configuration fields—including `source`, `target`, `headers`, `values`, `swap`, `push`, and `replace`—and packages them into a request configuration object.

## Request Construction Pipeline

The internal `issueAjaxRequest` function orchestrates the actual network call by delegating to four distinct responsibilities.

### 1. Context Resolution and Normalization

Before opening the connection, htmx determines exactly which element triggered the request and where the response should land. The `resolveTarget` function converts CSS selectors or direct element references into concrete DOM nodes. If a target is specified in the context object, it becomes the **targetOverride** for the response phase. This resolution step ensures that subsequent pipeline stages operate on verified DOM references rather than raw strings.

### 2. Header and Payload Assembly

htmx builds request metadata through `getHeaders` (**lines 4620‑4660**) and gathers form data via `getInputValues` (**lines 3780‑3810**).

The header construction automatically injects:
- `HX-Request: true` (identifies the request as originating from htmx)
- `X-Requested-With: XMLHttpRequest` (standard AJAX marker)
- Custom headers from the `hx-headers` attribute or the context object’s `headers` property
- CSRF/anti‑forgery tokens when the `hx-csrf` attribute is present

For the request body, `getInputValues` serializes form fields, `hx-vals`, and `hx-prompt` values. Depending on the verb and `Content-Type`, htmx encodes the payload as **FormData** or **JSON**. Non‑GET verbs receive the encoded data via `xhr.send(body)`, while GET requests append parameters to the URL through `normalizePath` (**lines 4460‑4560**).

### 3. XHR Lifecycle Management

Inside `issueAjaxRequest`, htmx instantiates a native `XMLHttpRequest` and applies security and configuration policies:
- **Domain restrictions**: The global `selfRequestsOnly` config (**lines 1919‑1925**) prevents requests to external origins
- **Credentials**: The `withCredentials` setting (**lines 1880‑1886**) controls cookie and authorization header transmission

Before calling `xhr.open(verb, url, true)`, htmx fires the `htmx:configRequest` event to allow extensions to mutate the XHR object. The `htmx:beforeRequest` event immediately follows; returning `false` from a handler aborts the request entirely. Once cleared, htmx sets the assembled headers with `xhr.setRequestHeader` and transmits the payload.

### 4. Response Processing and DOM Swapping

After the server responds, `processResponse` handles the result. The response text is parsed into DOM fragments via `makeFragment` (**lines 6063‑6145**), which extracts any `<title>` tags for history updates. The `getSwapSpecification` function determines the insertion strategy (e.g., `innerHTML`, `outerHTML`, `beforebegin`), then `swap` (**lines 6790‑6980**) performs the actual DOM manipulation, respecting `hx-swap`, `hx-select`, and `hx-select-oob` attributes.

Once the DOM update completes, htmx fires `htmx:afterSwap` and `htmx:afterRequest`, followed by internal cleanup via `cleanUpElement` to remove temporary state from processed elements.

## Practical Code Examples

You can trigger htmx AJAX requests either declaratively or programmatically.

```html
<!-- Declarative request targeting a specific DOM node -->
<button hx-get="/search?q=hello" hx-target="#results" hx-swap="innerHTML">
  Search
</button>

```

The programmatic equivalent uses the same pipeline:

```javascript
// Basic programmatic request
htmx.ajax('GET', '/search?q=hello', { target: '#results' });

```

For advanced use cases, pass a configuration object to override headers, inject additional values, and control history:

```javascript
htmx.ajax('POST', '/api/items', {
  source: '#new-item-form',           // Element providing form values
  headers: { 'X-My-Header': '123' },  // Custom request headers
  values: { extra: 'data' },          // Additional payload merged with form data
  swap: 'innerHTML',                  // Swap style override
  push: true                          // Push URL to browser history
});

```

## Summary

- **Context resolution**: `ajaxHelper` and `resolveTarget` convert selectors and configuration objects into concrete DOM references and request parameters.
- **Header and payload creation**: `getHeaders` assembles standard and custom headers, while `getInputValues` encodes form data as FormData or JSON.
- **XHR lifecycle**: `issueAjaxRequest` creates the `XMLHttpRequest`, enforces `selfRequestsOnly` security, applies `withCredentials`, and dispatches `htmx:configRequest` and `htmx:beforeRequest` events.
- **Response processing**: `makeFragment` parses HTML, `swap` updates the DOM according to `hx-swap` specifications, and lifecycle events signal completion.

## Frequently Asked Questions

### What is the main entry point for programmatic AJAX in htmx?

The `htmx.ajax` method, which aliases the internal `ajaxHelper` function at **line 307** of [`src/htmx.js`](https://github.com/bigskysoftware/htmx/blob/main/src/htmx.js), serves as the primary JavaScript API. It accepts an HTTP verb, URL, and optional context object, then delegates to `issueAjaxRequest` to execute the network call.

### How does htmx handle CSRF tokens in AJAX requests?

The `getHeaders` function (**lines 4620‑4660**) checks for the `hx-csrf` attribute and automatically appends the token to the request headers. You can also supply tokens manually via the `headers` property in the context object passed to `htmx.ajax`.

### Can I cancel or modify an AJAX request before it is sent?

Yes. Listen for the `htmx:configRequest` event to modify the underlying `XMLHttpRequest` before it opens, or handle `htmx:beforeRequest` to abort the request by returning `false`. Both hooks fire inside `issueAjaxRequest` before `xhr.send()` is called.

### What determines whether htmx sends FormData or JSON in the request body?

The encoding depends on the HTTP verb and the `Content-Type` header. `getInputValues` (**lines 3780‑3810**) gathers form fields and `hx-vals`, then `issueAjaxRequest` encodes the payload as **FormData** by default or as **JSON** when the `Content-Type` indicates JSON format. GET requests never include a body; parameters are appended to the URL via `normalizePath`.