NEWS

htmx library reaches version 4.0 and swaps XMLHttpRequest for fetch()

After skipping version 3 entirely, the project rewrites its network transport, adds native DOM morphing, and makes attribute inheritance explicit, changes that demand attention before upgrading in production.

htmx library reaches version 4.0 and swaps XMLHttpRequest for fetch()
Image: Redação iMasters

The htmx team released version 4.0.0 of the library, the first major update since 2.0, in 2024. The curious detail is that the project skipped version 3 entirely: Carson Gross, the creator of htmx, had publicly promised that there would never be an htmx 3, and summed up the reversal in a single word to InfoWorld: "Oops". Version 4.0 shipped after eight months of work and arrives with changes that go beyond a bigger version number: the network transport was rewritten, the DOM update engine gained native morphing, and attribute inheritance, which used to be implicit, now needs to be declared.

From XMLHttpRequest to fetch(): what it unlocks

Since it was created, htmx has used XMLHttpRequest to make the requests triggered by attributes such as hx-get and hx-post. In 4.0, that transport was replaced by the Fetch API. For most devs who already use the library, hx-get and hx-post keep behaving exactly as before, so this isn't a change that breaks day-to-day work outright. What it unlocks is native response streaming, something the old API didn't handle well, and the script stays lightweight, around 14KB. It's the technical foundation that underpins the two features the team itself highlights as the most important in the release.

Native morphing and the new hx-partial

The first of these features is morphing-based swaps, now built into the library and powered by idiomorph. Instead of replacing an entire block of HTML in the DOM, morphing calculates the diff and preserves state such as input focus during the update, something that has always been a weak point for anyone swapping HTML via AJAX without a framework.

The second is the hx-partial tag, a more organized way to do what used to require the "out of band swaps" pattern: a single server response can update several different targets on the page at once, without needing to scatter hx-swap-oob across every returned HTML fragment.

Attribute inheritance is now explicit, and that's the catch

The change that requires the most care during migration is attribute inheritance. Until now, an attribute defined on a parent element (such as hx-confirm or hx-headers) was automatically inherited by its children. In 4.0, that stopped being automatic: the attribute is only inherited if it carries the :inherited suffix.

html
<div hx-confirm:inherited="Are you sure?">
 <button hx-delete="/item/1">Delete</button>
</div>

A detailed piece published on DEV Community (cited by InfoQ) draws attention to the most dangerous scenario in this change: if your app uses hx-headers to propagate a CSRF token from the parent element to its children, and you don't add :inherited, the propagation simply stops working. There's no visual error, no exception in the console: the server starts rejecting requests with 403 while, on screen, nothing seems broken. It's the kind of bug that only shows up in production, in a write flow the QA team didn't test at that exact point in the component tree.

Renamed events and history without localStorage

Another structural change is the standardization of event names to the htmx:phase:action format. htmx:afterRequest became htmx:after:request, for example, which requires reviewing any custom event listener in your code. htmx also stopped saving DOM snapshots to localStorage for navigation history restoration; now it simply re-fetches the page, which fixes a classic problem where third-party scripts didn't survive the restoration of an old snapshot.

Beyond these, there are a few individual attribute renamings, such as hx-disable, which becomes hx-ignore, and a new default 60-second timeout for requests, something that could affect slow endpoints that currently rely on the client waiting indefinitely.

How to migrate without surprises

The htmx team made a decision that avoids the worst-case scenario for any library distributed via CDN without a pinned version: 4.0 was published under npm's next tag, not latest. That means anyone pointing to a CDN URL without a locked version isn't force-updated, and 2.x continues to receive support indefinitely. For those who want to migrate, there's a command-line checker that scans the project and flags inheritance issues, renamed attributes, and old event names:

npx htmx.org@4.0.0 upgrade-check -- ./templates

The team also published a full "What's New in htmx 4" guide and dedicated "skills" files for those using AI assistants in the upgrade process, an explicit acknowledgment that much of today's code migration goes through tools like Claude or Copilot before it goes through the dev.

Mixed reception: good with Go and SQLite, bad with those who want to separate layers

The community's reaction, according to InfoQ, was strong on both sides. A Hacker News thread topped 700 points, with developers praising the combination of Go, SQLite, and htmx, and one comment noting that AI assistants like Claude "understand htmx and work well with it". On Reddit, the launch post in r/htmx topped 300 upvotes.

On the other side, a developer with a background in .NET and Angular argued that htmx's model forces a mix of presentation and business logic directly into the HTML, and another agreed, saying his team is going back to React. It's the same old criticism of the hypermedia movement, which resurfaces with every major release of the library.

What changes for those who already run htmx in production

For those already running Django, Go, or Rails stacks with htmx on the front end, the good news is that migration isn't mandatory or forced by CDN. But it's not trivial either: the riskiest pairing on the list is attribute inheritance plus CSRF, because the symptom (a silent 403) only shows up after deployment. Before rolling out 4.0 on any real project, it's worth running upgrade-check, manually reviewing any hx-headers or hx-confirm that depends on inheritance, and mapping custom event listeners to the new phase:action pattern. Left out of InfoQ's coverage was any performance benchmark for the switch to fetch(), so anyone expecting streaming or payload numbers will still need to measure that on their own project.

Translated from the Brazilian Portuguese original · Read the original