HTMX Debugging Tips
When an HTMX request does not behave as expected, you need a systematic way to find the problem. HTMX provides built-in logging tools, and the browser's developer tools expose everything you need to diagnose any issue. This topic covers the most common problems, how to identify them, and how to fix them.
Enable HTMX Logging
Turn on HTMX's built-in logger with one line in the browser console. It prints every event, request, and response to the console:
htmx.logAll();
Run this in the browser console while testing your page. You will see every lifecycle event fire in real time:
Console output:
htmx:configRequest {path: "/search", verb: "get", target: #results}
htmx:beforeRequest {elt: button#search-btn, ...}
htmx:beforeSend {xhr: XMLHttpRequest, ...}
htmx:afterRequest {successful: true, xhr: XMLHttpRequest}
htmx:beforeSwap {target: div#results, ...}
htmx:afterSwap {target: div#results, ...}
To stop logging:
htmx.logNone();
Use the Network Tab
The browser's Network tab (F12 → Network) shows every HTTP request HTMX sends. Filter by "Fetch/XHR" to see only HTMX requests. For each request, inspect:
- Request URL: Is it the URL you intended?
- Request Method: GET, POST, PUT, or DELETE — is it correct?
- Request Headers: Is
HX-Request: truepresent? Is the CSRF token included? - Request Body: Are form values being sent correctly?
- Response Status: Is the server returning 200, or an error code?
- Response Body: Is the server returning valid HTML? Is it what you expect?
Debugging checklist via Network tab:
Issue: Nothing happens on click
Check: Does a request appear in Network? → No → hx-trigger or hx-get not set correctly
→ Yes → check response status and body
Issue: Response appears but goes to wrong place
Check: hx-target value — does the selector match an element on the page?
Issue: Request fires but page looks wrong
Check: hx-swap value — are you replacing innerHTML vs outerHTML correctly?
Common Problems and Fixes
Problem 1: No Request Fires on Click
Possible causes: 1. Script tag not loaded — check console for "htmx is not defined" 2. HTMX attribute typo — "hx-get" misspelled as "hx-gett" 3. JavaScript error before HTMX loads — check console for errors 4. Element added to DOM after HTMX initialized — call htmx.process(element) Fix: Open console and type htmx.version — if it returns undefined, HTMX is not loaded.
Problem 2: Request Fires but Gets a CSRF Error (403)
Cause: State-changing request (POST/PUT/DELETE) missing the CSRF token
Fix: Add CSRF token globally in htmx:configRequest event:
document.addEventListener('htmx:configRequest', function(e) {
e.detail.headers['X-CSRFToken'] = getCsrfToken();
});
Problem 3: Response Goes to the Wrong Element
Cause: hx-target selector does not match any element, or matches the wrong one
Debug:
document.querySelectorAll('#my-target') // Does it return the right element?
Fix: Make IDs unique. Use browser DevTools Element Inspector to confirm the ID.
Problem 4: New Content Has No HTMX Behavior
Cause: Content inserted by JavaScript (not HTMX) does not activate HTMX attributes
Fix: After inserting HTML manually, call:
htmx.process(document.getElementById('new-container'));
Problem 5: Form Values Not Sent
Cause: Input is outside the form element, or has no name attribute Fix: Every input that should be submitted must: 1. Be inside the triggering <form> element, OR 2. Be included via hx-include="#input-id" 3. Have a name attribute — inputs without name are not submitted
Problem 6: Double Request Fires
Cause: Both hx-trigger="submit" on the form AND a click event on the button
Both the form and the button have HTMX attributes
Fix: Put HTMX attributes on either the form OR the button — not both.
Problem 7: Infinite Scroll Never Fires
Cause: The sentinel element is never actually "revealed" (scrolled into view)
— usually because it has zero height, or is hidden by CSS
Fix: Give the sentinel visible height:
<div hx-get="..." hx-trigger="revealed" style="height:1px"></div>
HTMX Event Inspector
Listen to a specific event and log its full detail object to understand what data is available:
<script>
document.addEventListener('htmx:beforeSwap', function(event) {
console.log('Target:', event.detail.target);
console.log('Response HTML:', event.detail.serverResponse);
console.log('Status:', event.detail.xhr.status);
});
</script>
Checking HTMX Configuration
View the current HTMX configuration at any time in the console:
console.log(htmx.config);
Useful config properties to check:
| Property | Default | Description |
|---|---|---|
| htmx.config.defaultSwapStyle | innerHTML | Default hx-swap if not specified |
| htmx.config.timeout | 0 (no timeout) | Request timeout in milliseconds |
| htmx.config.historyCacheSize | 10 | Number of pages stored in history cache |
| htmx.config.selfRequestsOnly | false | Restrict requests to same origin when true |
Testing HTMX in Isolation
If you suspect a server issue, test HTMX by pointing it at a static HTML file for the response. Create a file called fragment.html with simple content and use it as the hx-get URL during debugging. If the swap works with the static file but not with the server response, the bug is in the server's output.
<!-- fragment.html (static test file) --> <p>This is a test fragment.</p> <!-- Test button pointing at static file --> <button hx-get="/fragment.html" hx-target="#output">Test Swap</button> <div id="output"></div>
Key Takeaway
Debugging HTMX starts with htmx.logAll() in the console for event tracing, and the Network tab for inspecting exact requests and responses. Most HTMX bugs fall into one of six categories: missing script, wrong selector, CSRF error, missing name attribute, duplicate triggers, or unprocessed dynamically-inserted content. Fixing any of these is straightforward once you identify the category through systematic inspection.
