htmx: Build Interactive Web Apps with HTML · Beyond the Basics

Upgrading to htmx 4

Plan an upgrade from htmx 2 to htmx 4: explicit inheritance, new event names, error swapping, renamed attributes and the upgrade checker.

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

What you'll build

An upgrade plan for the contacts app, and a version of the delete-row markup that behaves the same in htmx 2 and htmx 4.

The idea

htmx 4.0 shipped on 28 August 2026. htmx 2 stays supported indefinitely, and npm keeps 2.x as latest until some point in early 2027, so you can upgrade when it suits you. The changes that affect most apps:

Area htmx 2 htmx 4
Transport XMLHttpRequest fetch() (streaming responses)
Inheritance implicit explicit: hx-target:inherited="..."
4xx/5xx responses not swapped swapped (except 204, 304); hx-status:422="..." for control
Events htmx:afterRequest htmx:after:request
Attributes hx-disable, hx-disabled-elt hx-ignore, hx-disable
History snapshots in sessionStorage re-fetches the page on Back
Timeout none 60 seconds
Extensions hx-ext="sse" include the script; no hx-ext

Steps: read the official guide, run npx [email protected] upgrade-check -- ./templates, rename hx-disable → hx-ignore then hx-disabled-elt → hx-disable, add :inherited (or set htmx.config.implicitInheritance = true temporarily), rename event listeners, and review error handling.

The code

Writing attributes on each element instead of relying on inheritance gives markup that works the same in both versions:

Version-neutral delete buttons (no inheritance)Try it yourself
<table><tbody>
  <tr><td>Ada</td><td><button hx-delete="/contacts/1" hx-confirm="Delete Ada?"
      hx-target="closest tr" hx-swap="outerHTML">Delete</button></td></tr>
  <tr><td>Grace</td><td><button hx-delete="/contacts/2" hx-confirm="Delete Grace?"
      hx-target="closest tr" hx-swap="outerHTML">Delete</button></td></tr>
</tbody></table>

<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
  server.delete("/contacts/:id", () => "");
</script>

In htmx 4 the inherited version needs the suffix:

<tbody hx-confirm:inherited="Delete this contact?" hx-target:inherited="closest tr" hx-swap:inherited="outerHTML">

Common mistakes

  • Renaming hx-disabled-elt to hx-disable before renaming the old hx-disable to hx-ignore: the two meanings get mixed up.
  • Forgetting that error responses now swap: an error page can land in a small target. Add hx-status rules or return small error fragments.
  • Upgrading before your extensions (SSE, WebSockets, others) support htmx 4.
  • Leaving implicitInheritance on permanently instead of finishing the migration.

Key points

  • htmx 2 remains supported; upgrade deliberately.
  • Inheritance, event names and error swapping are the main changes.
  • Use the upgrade checker and rename attributes in the right order.

Further reading: What's new in htmx 4 · htmx 4.0.0 release · Tutorial: Upgrading to htmx 4

Practice questions

What is the largest change when moving from htmx 2 to htmx 4?

Answer

Attribute inheritance becomes explicit: attributes that should apply to descendants need the :inherited suffix.

In which order should you rename hx-disable and hx-disabled-elt when upgrading?

Answer

First hx-disable → hx-ignore, then hx-disabled-elt → hx-disable; the other order mixes the two meanings.

How does error-response handling differ between htmx 2 and 4?

Answer

htmx 2 doesn't swap 4xx/5xx; htmx 4 swaps everything except 204 and 304, with hx-status for per-code control.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?