htmx Tutorial · htmx Basics · Lesson 3 of 30

How htmx Works

The request-swap cycle step by step, what htmx sends to the server, and how to read the request log.

Create a free account to save this page, take notes and track your progress. Sign in

The htmx cycle

Every htmx interaction follows the same five steps:

  1. Trigger: an event happens on an element with an htmx attribute (by default a click; a change for inputs; a submit for forms).
  2. Request: htmx sends an HTTP request, using the method and URL from hx-get, hx-post, etc. It includes any form values and some extra request headers.
  3. Response: the server returns HTML (usually a small fragment, not a whole page).
  4. Swap: htmx puts that HTML into the target element (hx-target, by default the element itself), in the way hx-swap says (by default it replaces the target's inner HTML).
  5. Settle: htmx processes the new content, so htmx attributes in the response work too.

Step 5 is important: the server's HTML can contain more hx- attributes, and the cycle continues from there.

Responses can contain more htmxTry it yourself
<div id="step">
  <p>Step 1 of 3</p>
  <button hx-get="/step/2" hx-target="#step">Next</button>
</div>

<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
  // Each response contains a new button with its own hx-get: the cycle continues.
  server.get("/step/:n", (req) => {
    const n = Number(req.params.n);
    return n < 3
      ? `<p>Step ${n} of 3</p><button hx-get="/step/${n + 1}" hx-target="#step">Next</button>`
      : `<p>Step 3 of 3. Done!</p><button hx-get="/step/1" hx-target="#step">Start again</button>`;
  });
</script>

Headers htmx adds to each request

Your server can tell an htmx request from a normal page load by its headers:

Header Value
HX-Request always true
HX-Trigger the id of the element that was triggered, if it has one
HX-Trigger-Name the name of the triggered element, if it has one
HX-Target the id of the target element, if it has one
HX-Current-URL the current URL of the browser
HX-Boosted true if the request comes from a boosted link or form

The most useful one is HX-Request: the same URL can return a full page to a normal visit and just the fragment to htmx. This example shows the headers the server received:

What the server receivesTry it yourself
<button id="who" name="greeting" hx-get="/headers" hx-target="#out">Send a request</button>
<pre id="out"></pre>

<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
  server.get("/headers", (req) => {
    const h = req.headers;
    return server.escape(`HX-Request: ${h["hx-request"]}\nHX-Trigger: ${h["hx-trigger"]}\n` +
                         `HX-Trigger-Name: ${h["hx-trigger-name"]}\nHX-Target: ${h["hx-target"]}`);
  });
</script>

What if the server returns an error?

In htmx 2, responses with status 4xx or 5xx are not swapped by default: the page stays as it was and htmx fires an htmx:responseError event. A 204 No Content response is not swapped either. You'll learn how to show error messages on the forms page.

Test yourself

In htmx 2, what happens by default when the server answers with status 500?

  1. Nothing is swapped and htmx fires htmx:responseError
  2. The error page replaces the whole document
  3. htmx retries the request three times
  4. The response is swapped into the target like any other
Show answer

Answer: Nothing is swapped and htmx fires htmx:responseError

4xx and 5xx responses aren't swapped in htmx 2 unless you change htmx.config.responseHandling.

Was this page helpful?