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: true present? 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:

PropertyDefaultDescription
htmx.config.defaultSwapStyleinnerHTMLDefault hx-swap if not specified
htmx.config.timeout0 (no timeout)Request timeout in milliseconds
htmx.config.historyCacheSize10Number of pages stored in history cache
htmx.config.selfRequestsOnlyfalseRestrict 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.

Leave a Comment

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