# htmx 4: What's New, What Breaks, Why It's Not latest

Published: August 28, 2026
Tags: webdev, javascript, htmx, opensource

htmx 4.0 is out: fetch() internals, explicit inheritance, no more history cache. What breaks, how to migrate, and why 2.x stays latest on npm until early 2027.

htmx 4.0.0 [shipped today, August 28, 2026](https://four.htmx.org/announcements/2026-08-28-htmx-4.0.0-is-released). The ajax core now runs on `fetch()` instead of `XMLHttpRequest`, attribute inheritance is explicit, the back button issues a real request instead of restoring a cached snapshot, and error responses can be routed by status code. It is a major release that breaks real things, on purpose. And it comes with an unusual instruction: nobody has to move. On npm, 2.x keeps the `latest` dist-tag until early 2027, 4.0 lives under `next`, and htmx 2 stays supported indefinitely.

## TL;DR

- Every request htmx makes now goes through `fetch()`; `XMLHttpRequest` is out, along with the XHR-specific events.
- Attribute inheritance is explicit: attributes like `hx-confirm` reach descendants only when you add the `:inherited` modifier.
- The history DOM cache is gone. The back button re-fetches the page from the server instead of restoring a stored snapshot.
- Error responses swap by default, and the new `hx-status` attribute routes 4xx and 5xx responses by status code to their own targets.
- On npm, htmx 2.x keeps the `latest` dist-tag until early 2027, and 4.0 lives under the `next` dist-tag, so nobody upgrades by accident.

## What changed in htmx 4

htmx 4 is a ground-up rework of the library's internals and a cleanup of its accumulated defaults. Carson Gross, htmx's creator, announced it in an essay called [The fetch()ening](https://htmx.org/essays/the-fetchening/) in November 2025, together with the first alpha. A string of betas followed through 2026, and the [final release landed today](https://github.com/bigskysoftware/htmx/releases).

The big majors I covered this year, [Astro 7](/blog/astro-7) and [pnpm v12](/blog/pnpm-v12-rust-rewrite), rewrote their engines precisely so that nothing would change for you: same surface, faster internals. htmx 4 is the other kind of major. Moving to `fetch()` gave the project a reason to revisit design decisions that had been locked in for years, and the team took it. Implicit inheritance, the history cache, silently ignored error responses: all gone. That is why the release strategy matters as much as the feature list, and I will get to it.

## The fetch() rewrite

Every request htmx makes now goes through `fetch()` rather than `XMLHttpRequest`. You do not call either API yourself when you write htmx, so on a quiet day you will not notice. The visible consequences are at the edges:

- **XHR-specific events are gone.** `htmx:xhr:loadstart`, `htmx:xhr:progress`, and `htmx:xhr:abort` have no `fetch()` equivalent and were removed.
- **Events follow a new `htmx:phase:action` naming scheme.** If you listen to htmx lifecycle events in JavaScript, every listener needs a rename.
- **A default request timeout exists now.** Requests time out after 60 seconds; in 2.x they could hang forever. The config option moved from `timeout` to `defaultTimeout`.

The event renames look like this:

| htmx 2.x | htmx 4 |
| --- | --- |
| `htmx:beforeRequest` | `htmx:before:request` |
| `htmx:afterRequest` | `htmx:after:request` |
| `htmx:beforeSwap` | `htmx:before:swap` |
| `htmx:afterSwap` | `htmx:after:swap` |
| `htmx:configRequest` | `htmx:config:request` |
| `htmx:sendError`, `htmx:swapError`, `htmx:targetError`, `htmx:timeout` | `htmx:error` |

That last row is a real simplification: four error events collapse into one `htmx:error`.

Extensions changed even more. The announcement puts it plainly: "Switching to `fetch()` internally let us rethink how extensions can and should work." The old `htmx.defineExtension()` API is replaced by `htmx.registerExtension()`, and the `hx-ext` attribute is gone entirely: loading an extension's script is enough. There is a dedicated [extension migration guide](https://four.htmx.org/docs/extension-htmx-4-migration-guide) for authors. The payoff is streaming: new first-party extensions stream HTML into the page over server-sent events (`hx-sse`), WebSockets (`hx-ws`), and `multipart/mixed` responses (`hx-multipart`).

## Attribute inheritance is now explicit

In htmx 2, attributes like `hx-confirm` and `hx-target` silently inherited from parent elements. Convenient until it wasn't: a `hx-confirm` on a container would attach confirmation dialogs to descendants you forgot about, and `hx-disinherit` existed purely to fight this. In htmx 4, inheritance only happens when you ask for it with the `:inherited` modifier:

```html
<div hx-confirm:inherited="Are you sure?">
  <button hx-delete="/account">Delete my account</button>
</div>
```

Without the modifier, the attribute applies only to the element it sits on. `hx-inherit` and `hx-disinherit` are both removed, since explicit inheritance makes them pointless. If your codebase leans heavily on the old behavior, setting `implicitInheritance` to `true` in the htmx config restores it while you migrate.

Of everything in this release, this is the change I would have asked for. Implicit inheritance was the closest thing htmx had to action at a distance, and debugging it meant walking up the DOM tree in your head.

## The back button is a real request now

htmx 2 handled history by snapshotting the DOM into browser storage (localStorage originally, sessionStorage [since 2.0.5](https://github.com/bigskysoftware/htmx/releases/tag/v2.0.5)) and restoring the snapshot on back navigation. That cache is exactly the kind of client-side state that drifts from reality, and [InfoWorld's coverage](https://www.infoworld.com/article/4150864/htmx-4-0-hypermedia-finds-a-new-gear.html) of the 4.0 line put it bluntly: "It turned out to be brittle."

htmx 4 drops the cache entirely. Per the [announcement](https://four.htmx.org/announcements/2026-08-28-htmx-4.0.0-is-released), on back navigation htmx re-fetches the page from the server and swaps it into `<body>`. Your back button now shows what the server says the page is, not what the page happened to look like when you left it. The `hx-history` attribute and `historyCacheSize` config are gone with it. If you genuinely need snapshot behavior, an `hx-history-cache` extension brings it back as an opt-in.

## Route error responses with hx-status

htmx 2 refused to swap 400 and 500 responses by default, which meant server-rendered validation errors needed workarounds. htmx 4 flips the default: 4xx and 5xx responses swap like any other (only 204 and 304 are no-swap by default), and a new `hx-status` attribute routes specific status codes to their own target and swap behavior:

```html
<form hx-post="/signup"
      hx-status:422="target:#validation-errors"
      hx-status:5xx="target:#server-error"
      hx-status:503="swap:none">
```

Matching goes from specific to general: an exact code like `503` wins over a single-digit wildcard like `50x`, which wins over a range wildcard like `5xx`. A 422 with the form's field errors rendered server-side can now land in the right spot with one attribute, which is what hypermedia apps do all day. If you depended on the old ignore-errors behavior, setting the `noSwap` config to `[204, 304, '4xx', '5xx']` restores it.

## What else is new in htmx 4

The [what's new page](https://four.htmx.org/docs/whats-new-in-htmx-4) lists more than I can cover, but a few stand out. Morph swaps are built in: `innerMorph` and `outerMorph` use the idiomorph algorithm and need no extension. New swap styles `textContent` and `delete` join the existing set. A new `<hx-partial>` element lets one response update several targets, with each partial carrying its own target and swap. View Transitions support is there but disabled by default; flip the `transitions` config option to turn it on.

The one that caught my eye: a new `hx-query` attribute issues an HTTP QUERY request, the [safe-reads-with-a-body method](/blog/the-new-http-query-method) I wrote about in July after RFC 10008 landed. Watching a brand-new HTTP verb show up as a first-class attribute in a hypermedia library within months is the spec pipeline working the way it should.

## The breaking changes in htmx 4

The [migration guide](https://four.htmx.org/docs) is thorough, and the headline breaking changes are the three above: explicit inheritance, error responses swapping by default, and server round-trip history. Beyond those:

- **Two attributes trade names, and order matters.** `hx-disable` becomes `hx-ignore`, and `hx-disabled-elt` becomes `hx-disable`. The old name gets reused, so the guide says to rename `hx-disable` to `hx-ignore` first or you will migrate one attribute into the other.
- **Removed attributes:** `hx-ext`, `hx-inherit`, `hx-disinherit`, `hx-history`, `hx-vars` (use `hx-vals` with the `js:` prefix), `hx-params` (use the `htmx:config:request` event), `hx-request`, and `hx-prompt` (now an extension).
- **Config options were renamed or culled.** `timeout` is `defaultTimeout`, `defaultSwapStyle` is `defaultSwap`, `globalViewTransitions` is `transitions`. A long list of options is simply gone, including `historyCacheSize`, `defaultSwapDelay`, and the settling-class options.
- **Validation events are removed.** `htmx:validation:validate`, `failed`, and `halted` are out; native browser validation is the path forward.
- **Every event listener needs the new names.** See the table above.

htmx ships a scanner that finds most of this for you:

```sh
npx htmx.org@4.0.0 upgrade-check -- ./path/to/project/root
```

It walks your templates and prints deprecated patterns with file and line numbers. Run it before deciding how big your migration actually is; for a small app the answer may be an afternoon.

## Why 4.0 is not latest on npm

Here is the part I find genuinely rare. As of today, the [htmx.org package](https://www.npmjs.com/package/htmx.org) has `latest` pointing at 2.0.10 and 4.0.0 published under `next`. That is deliberate, and the announcement spells out why:

> we are not marking 4.0 as `latest` in NPM because we do not want to force-upgrade users who are relying on non-versioned CDN URLs for htmx. Instead, 2.x will remain `latest` and the 4.0 line will remain `next` until some point in early 2027.

Plenty of production sites load htmx from a CDN URL that resolves whatever `latest` is. Flipping the tag on release day would silently ship breaking changes to all of them. So the tag waits several more months while 4.0 is, by every other measure, released and stable today.

The announcement frames the whole release this way: "we have made explicit choices that we feel will put htmx-based applications in a good spot for being 100-year web services." You can roll your eyes at the century talk, but the mechanics back it up. htmx 2 "will continue to be supported indefinitely so don't feel any pressure to upgrade," per the same announcement. Compare that with the upgrade treadmill most of the ecosystem runs on, where staying one major behind makes you a straggler within months. htmx is telling you the old version is fine, forever, and meaning it.

## Should you upgrade to htmx 4?

For a new project, start on 4. The defaults are better: explicit inheritance, error responses you can actually handle in hypermedia, history that cannot go stale. Install it with `npm install htmx.org@next` or pin the 4.0.0 CDN URL, since `latest` will hand you 2.x until early 2027.

For an existing app, there is no clock. Run the `upgrade-check` scanner to size the work, and if the report is short, do the migration in an isolated [git worktree](/blog/git-worktrees-are-underrated) and shake out the event renames. If the report is long, htmx 2 remains supported and maintained, and the team has said so in writing.

The technical changes are good. The release discipline is the story I will remember: a project that shipped a breaking major and then deliberately kept it off the `latest` tag so nobody gets upgraded by accident. That is what taking backwards compatibility seriously looks like.
