---
name: htmx-debugging
description: Use when diagnosing htmx issues, troubleshooting requests that aren't firing, swaps that aren't happening, events that aren't triggering, or any unexpected htmx behavior.
argument-hint: "[description of the problem]"
---
# htmx 4 Debugging Guide
## Quick Diagnostic Checklist
Work through these in order -- most issues fall in the first few:
1. **Is htmx loaded?** Check for `htmx` global in console, verify script tag
2. **Is the element processed?** Check `element._htmx` property exists
3. **What htmx version?** Check `htmx.version` -- the htmx 4 API is very different from htmx 2
4. **Is the trigger correct?** Defaults: `click` for most, `change` for inputs/selects/textareas, `submit` for forms
5. **Is the target correct?** Verify the CSS selector matches an existing element
6. **Does the server return HTML?** Not JSON -- check Content-Type and response body
7. **Check the response status** -- htmx 4 swaps ALL responses except 204 and 304 by default
8. **Is inheritance set up?** htmx 4 requires `:inherited` modifier on parent attributes
9. **For extensions:** Is the script loaded after `htmx.js`? If you set the `extensions` whitelist, the
registration name must be in it. An unset whitelist allows every extension
## Enable Debug Logging
Errors and warnings flow to `console.error` / `console.warn` by default. To also surface every event htmx dispatches:
```html
```
or in the console:
```js
htmx.config.logAll = true;
```
Observability tools (Sentry, DataDog RUM, LogRocket, etc.) capture `console.*` automatically, so htmx logs flow into your existing pipeline without any extra setup.
## Event Monitoring Snippet
Paste this in the console to monitor the request/swap lifecycle:
```js
['htmx:config:request', 'htmx:before:request', 'htmx:after:request',
'htmx:before:swap', 'htmx:after:swap', 'htmx:finally:swap', 'htmx:error', 'htmx:finally:request']
.forEach(evt => document.body.addEventListener(evt, e => {
console.log(evt, e.detail?.ctx?.request?.action, e.detail?.ctx?.response?.status, e.detail);
}));
```
To monitor what events a specific element is firing:
```js
monitorEvents(htmx.find("#theElement"));
```
## Common Issues and Solutions
### Request Not Firing
**Check the trigger:**
- Is the event actually happening? Use `monitorEvents()` on the element
- Default triggers differ by element type -- an `` won't fire on `click`
- If using `hx-trigger="load"`, was the element in the DOM before htmx initialized?
**Check synchronization:**
- `hx-sync` may be dropping or queuing the request
- Check for `hx-sync="closest form"` or similar that might block it
**Check confirmation:**
- `hx-confirm` blocks until confirmed -- including `js:` async confirmation
- An `htmx:confirm` event listener calling `preventDefault()` without calling `issueRequest()` will block forever
**Check for `hx-ignore`:**
- A parent element with `hx-ignore` disables htmx for all children
**Dynamic content:**
- Elements added to the DOM after page load need `htmx.process(element)` to initialize htmx behavior
- Or use `htmx.onLoad()` to set up a callback for new content
### Swap Not Happening
**Check response status:**
- `204` and `304` do NOT swap by default (controlled by `htmx.config.noSwap`)
- In htmx 4, 4xx and 5xx responses DO swap by default (unlike htmx 2!)
- If you need htmx 2 behavior: `htmx.config.noSwap = [204, 304, '4xx', '5xx']`
**Check the target:**
- Does the `hx-target` CSS selector match an existing element?
- Use browser devtools to run `document.querySelector("your-selector")` to verify
**Check `hx-swap`:**
- `hx-swap="none"` explicitly prevents swapping
- `hx-swap="delete"` deletes the target regardless of response
**Check `hx-select`:**
- If set, only matching elements from the response are used
- If nothing matches, nothing gets swapped
**Check event listeners:**
- An `htmx:before:swap` listener calling `preventDefault()` will cancel the swap
### Wrong Content Being Swapped
**Check selectors:**
- `hx-select` might be matching the wrong element in the response
- `hx-target` might point to the wrong element
**Check for OOB/partial interference:**
- `hx-swap-oob` in the response swaps content by ID independently
- `` tags in the response swap into their own targets
- In htmx 4, OOB swaps happen AFTER the main content swap (changed from htmx 2)
**Check response headers:**
- `HX-Retarget`, `HX-Reswap`, `HX-Reselect` override client-side attributes, and htmx applies them before `hx-status`
- `hx-status:XXX` attributes can change target, swap, select or history handling for one status code.
htmx tries the exact code first, then `NNx`, then `Nxx`, and stops at the first match
### Extension Not Working
1. Is the extension script loaded AFTER htmx.js?
2. If you set the `extensions` whitelist, is the extension name in it? An unset whitelist allows every extension
3. Does the name match exactly? It is case-sensitive, and the registration name is not always the file name.
`hx-sse.js` registers as `sse`, `hx-preload.js` as `preload`, `htmx-2-compat.js` as `compat`
4. Check console for registration errors
5. htmx 4 extensions use `htmx.registerExtension()` not `htmx.defineExtension()` -- make sure you have an htmx 4 compatible extension
### Inheritance Not Working
The #1 gotcha in htmx 4:
```html
```
- htmx 4 requires `:inherited` modifier by default
- Set `htmx.config.implicitInheritance = true` to get htmx 2 behavior
- Check that it's on the PARENT, not the child
### History/URL Issues
- `hx-push-url` and `hx-replace-url` require the URL to return a full page when accessed directly
- History restoration in htmx 4 does a full page request (no localStorage/sessionStorage cache)
- Set `htmx.config.history = "reload"` to do hard browser reloads on back/forward
- `hx-status` attributes with `push:false` can prevent URL updates on errors
### CSS Transitions Not Working
- CSS transitions rely on element ID stability across swaps -- keep `id` attributes consistent
- `htmx-swapping` class is applied before swap, `htmx-settling` after
- For View Transitions API: enable with `htmx.config.transitions = true` or `hx-swap="... transition:true"`
- Morphing (`innerMorph`/`outerMorph`) preserves animations better than `innerHTML`/`outerHTML`
### Form Data Not Included
- `GET` and `DELETE` requests do NOT include enclosing form data by default in htmx 4
- Fix: add `hx-include="closest form"` to include form values
- Non-GET/DELETE requests (POST, PUT, PATCH) DO include enclosing form values automatically
- Check `hx-vals` syntax: it takes HCON (`key:value, other:2`), which also accepts JSON. Use the `js:` prefix for dynamic values
### htmx 2 Code Not Working in htmx 4
Quick compatibility fixes:
1. Add `htmx.config.implicitInheritance = true` (restores automatic inheritance)
2. Add `htmx.config.noSwap = [204, 304, '4xx', '5xx']` (restores htmx 2 swap behavior)
3. Replace `hx-ext="name"` with `