This article is published in English.
Upgrading to htmx 4: Fetch, Explicit Inheritance and What Breaks
Understand the htmx 4 architecture changes, from the Fetch-based core and explicit attribute inheritance to error swaps, morphing and history, and plan a safe migration.
Htmx rests on a simple, deliberately unfashionable bet: the server returns HTML, the browser drops that HTML into the page, and a capable application emerges without mirroring the whole interface as client-side state. Version 4 keeps that bet intact while rebuilding the machinery underneath it around modern browser primitives. This guide explains what changed and why, which changes can silently break an existing app, and how to run the upgrade as a structured audit rather than a version bump. Details reflect htmx 4 as documented at the time of writing; confirm specifics against the current release notes before migrating.
The contract htmx 4 preserves
It helps to recall what htmx replaces. A typical client-rendered app asks an API for JSON, keeps that data in JavaScript, renders components from it, and continually reconciles local state with the server. Htmx cuts out most of that middle layer. The server answers with the representation the user actually needs, which is HTML.
Consider a button annotated with attributes along the lines of hx-post, a target such as #task-list, and a swap strategy that appends. When someone clicks it, htmx collects the request context, sends it to the server, parses the markup that comes back, and appends that markup to the task list. Validation, authorization, persistence and presentation stay on the server. Interaction, navigation, focus and the document itself stay with the browser.
That is more than a terse way to call fetch(). The attributes describe a hypermedia control: what action is available, where it is sent, and how the resulting representation should enter the current page. The fragment that comes back can itself contain links and forms advertising the next valid actions, exactly as a full page would. HTML, in other words, remains the application protocol; htmx merely coordinates the round trip.
Version 4 leaves that public surface almost boringly stable. What it reorganizes is the lifecycle beneath it, The outcome is not a new front-end framework but tighter rules for how HTML, HTTP, the DOM and the state-owning server cooperate.
A core rebuilt on the Fetch API
Earlier versions relied on XMLHttpRequest. That made sense when htmx had to run across an older browser landscape, and XHR exposed upload progress events that some applications depended on. Over time, though, that compatibility decision hardened into architectural debt. The htmx team rewrote the request path around the promise-based Fetch API, informed by experiments with a smaller sibling project called Fixi and with streaming HTML.
A predictable event naming scheme
The cleaner pipeline shows up in the event model. Event names now follow one consistent pattern, htmx:phase:action, optionally extended with a sub-action (htmx:phase:action:sub-action). Because the phase comes first, you can tell at a glance whether a listener fires before or after the step it names.
Every request-related event also receives the same context object. Extensions and listeners no longer have to piece together a grab bag of event-specific details to find the source element, the request configuration, the response and the pending swap; it is all in one place. Requests additionally get a finally phase that fires regardless of outcome: success, failure or cancellation. That is the natural home for cleanup such as hiding spinners or re-enabling buttons.
If your codebase listens for htmx events by name, every listener needs review, because the old names do not match the new scheme.
Wrappers retired in favor of the platform
Htmx 4 also drops helper functions that duplicated APIs browsers now provide reliably:
htmx.addClass()gives way toelement.classList.add()htmx.closest()gives way toelement.closest()htmx.remove()gives way toelement.remove()
This is healthy subtraction. A small library should not carry convenience APIs forever once the platform has absorbed them, and the replacements are standard DOM calls any developer already knows.
Attribute inheritance is now opt-in
The most consequential migration change has nothing to do with Fetch. Htmx 4 stops inheriting most attributes implicitly from ancestor elements.
Previously, an attribute placed on a container, for instance a target, a confirmation prompt or a set of request headers, would quietly apply to every htmx-powered descendant. In version 4 you must declare that reach explicitly by adding an :inherited suffix to the attribute name on the parent. A descendant that wants to extend an inherited value or selector, rather than overwrite it, can use the :append suffix.
The suffix is not decoration. It tells anyone reading the template that the parent's attribute is intentionally part of its children's behavior. This pushes htmx further toward locality of behavior: the nearer a declaration sits to the element it governs, the less invisible context a reader has to reconstruct. Shared behavior is still possible; its scope is simply announced where it is declared.
Why this is the riskiest part of the upgrade
The change also creates a quiet failure mode. Suppose a CSRF header has always been defined on a layout wrapper. After the upgrade the page renders exactly as before, but requests from child elements no longer carry the header and start being rejected by the server. Nothing looks broken until someone submits a form.
Htmx provides an official upgrade checker that scans templates and scripts for implicit inheritance, outdated event names, removed attributes and obsolete APIs. Use its report as a starting list, not a guarantee, and then exercise the real request paths, especially anything guarded by headers, confirmations or shared targets.
Error responses become swappable fragments
In htmx 2, responses with 4xx or 5xx status codes were not swapped by default. Htmx 4 reverses that: it swaps every HTTP response except 204 No Content and 304 Not Modified.
When different status families should land in different places or use different swap rules, the new hx-status attribute lets you configure that per status class, for example sending validation errors to an inline message area while server errors go to a page-level banner.
The real consequence lands on the server. Every error response must now be a valid fragment for whatever element will receive it. A full-page stack trace or a bare JSON error body will be inserted into the DOM as-is. Status codes keep their HTTP meaning for caches, logs and clients, while the HTML body carries the presentation and, ideally, the next action the user can take, such as a corrected form.
Updating several regions from one response
A single server-side operation often needs to change more than the element the user clicked. Posting a message might append it to a timeline, bump an unread counter and replace the pagination control. Htmx has long offered out-of-band swaps for this: elements in the response marked out-of-band replace matching elements elsewhere in the document.
Htmx 4 adds a more explicit tool, the <hx-partial> element. Each partial declares its own target and swap strategy, so the response reads as a list of clearly addressed updates.
Ordering is defined as well. The main response is swapped first; partials and out-of-band elements follow in document order. That encourages making each update meaningful on its own instead of depending on side effects from an earlier DOM change in the same response.
This amounts to lightweight response orchestration. The server can describe every visible consequence of one operation in one response, without returning JSON and leaving client code to distribute fields across components.
Morphing swaps that keep browser state alive
Replacing innerHTML is easy to reason about, but it throws away state the browser owns. A text field can lose its selection, focus can jump away, a video can restart, and a custom element can be torn down and recreated even when most of its markup is unchanged.
Htmx 4 ships innerMorph and outerMorph swap modes built on an improved version of the Idiomorph algorithm. Instead of discarding the target subtree, a morph compares old and new nodes and applies the smallest reasonable set of changes. Nodes that match keep their identity, and with it their focus, selection, playback position and internal state.
Morphing is not automatically the better choice. For a simple fragment, wholesale replacement is often safer and easier to debug. Morphing pays off when the target contains live form controls, custom elements, media or third-party widgets whose identity matters. Htmx also exposes selectors for skipping whole nodes, or just their children, during a morph, which is a practical way to protect stateful islands such as an embedded map or rich-text editor.
The general principle is worth keeping even outside htmx: the server decides what the HTML should be, and the browser preserves the physical DOM objects that ought to survive the transition.
Streaming lives in extensions, not the core
Fetch gives htmx a much better foundation for streamed responses, but the core does not impose one streaming protocol on everyone. Instead, htmx 4 ships separate, focused extensions for Server-Sent Events, WebSockets and multipart responses.
The multipart one, hx-multipart, understands multipart/mixed and multipart/parallel bodies. Each part can carry HTML along with its own HX-* action headers. That lets a server send an immediate placeholder, then stream additional sections as slow work finishes, targeting each one individually, all without a hand-built client-side message bus.
Choosing among them follows the shape of the data flow:
- SSE suits ordered, one-way updates pushed from the server.
- WebSockets suit genuinely bidirectional messaging.
- Multipart suits a single HTTP operation that yields several representations over time.
All three feed into the same swap machinery, so the rest of your markup does not care which transport delivered a fragment.
A simpler extension model
Keeping streaming out of the core keeps the core small, and the extension boundary has become more capable in return. Extensions in htmx 4 register themselves directly and can hook into the request, response and swap phases. You activate one simply by including its script; the hx-ext attribute no longer exists. If you want a hard boundary, configuration lets you restrict which extension names are permitted on a site.
HCON: a compact notation for attribute options
As attributes accumulated options, htmx needed a syntax less noisy than JSON embedded in an attribute value. The answer is HCON, the htmx Configuration Object Notation. It supports space-separated key-value pairs, bare flags as booleans, numbers, quoted strings and dotted keys for nesting.
JSON is still accepted, which is convenient when a server already generates configuration. HCON targets hand-written markup: terse enough to scan, yet structured enough that htmx does not need a separate ad hoc parser for every attribute. The same notation is used across triggers, swap modifiers, request configuration, headers, values and the HX-Location response header, so learning it once pays off everywhere.
hx-live for the client state that remains
Hypermedia does not remove every piece of local interactivity. A dropdown opens before any request is made. A character counter updates on each keystroke. Tabs, disclosure widgets and temporary selections are usually the browser's business.
The new hx-live extension covers those cases with a small, DOM-centered scripting layer. It offers a query helper, directional selectors for finding nearby elements, DOM utilities, async helpers, typed access to attributes and data values, and reactive bindings such as :text, :class and :hidden.
Its defining rule is philosophical: the DOM is the state store. A reactive expression reads state from nearby elements and updates nearby presentation. Anything durable remains server-owned and reaches the page as HTML, just as before.
Think of hx-live as a release valve for small UI chores, not an invitation to grow a second application inside the browser. Reach for it when you would otherwise write repetitive event-listener boilerplate for ephemeral UI. When state must survive navigation, be shared between users, enforce permissions or take part in transactions, it belongs on the server.
History navigation re-fetches instead of restoring snapshots
Htmx 2 stored history snapshots in localStorage. Restoring one could bring back DOM mutations made by unrelated scripts without bringing back the JavaScript runtime state that created them. The page looked interactive but was really a fossilized DOM: widgets rendered, yet nothing was wired up behind them.
Htmx 4 drops that default cache. On back and forward navigation it fetches the page again and swaps the result into <body> or into a designated history element. Proper HTTP caching headers can make that request nearly free while still handing scripts a clean document to initialize against.
Applications that genuinely need local snapshots can load the hx-history-cache extension, which uses sessionStorage and makes the behavior explicit. The pattern repeats throughout the release: a fresh representation is the default, and local reconstruction is an opt-in capability with a name.
Treat the migration as a behavioral audit
The safest upgrade is not a blind package swap but a short review of every place where behavior crosses markup boundaries. Most pages keep their markup unchanged; the work concentrates in implicit inheritance, event listeners, response handling, history and extensions.
Pin an exact htmx 4 version first, then run the official upgrade checker described in the migration documentation. Work through its findings in an order that avoids collisions between renamed attributes:
- Rename the old
hx-disable, which meant "ignore this subtree", tohx-ignore. - Only then rename
hx-disabled-eltto the newhx-disable. Doing these two steps in the opposite order would turn one attribute into the other by accident. - Add
:inheritedwherever a parent must keep influencing descendants, paying particular attention to headers, confirmations, targets and includes. - Update event listener names and replace removed helpers with native browser APIs.
- Test
4xxand5xxresponses, since they are now swapped by default, and make sure each one returns a sensible fragment. - Test every
hx-delete, which no longer sends the enclosing form's data unless you ask for it withhx-include. - Test back and forward navigation, out-of-band ordering, timeouts, request queues and every extension you use, remembering that
hx-extis gone.
When htmx 4 is the right tool
The headline change is Fetch, but the unifying theme is explicitness. An attribute reaches descendants only when its declaration says so. A failed request's HTML is shown to the user by default, and you configure the exceptions. Responses that touch several regions spell out where each piece goes and how it is swapped. Back and forward navigation loads a fresh page unless you deliberately enable a named snapshot cache. Extensions plug in through a single shared lifecycle, and standard DOM methods take over wherever htmx used to ship its own helpers.
Together these reduce hidden behavior without pushing application state into a client framework. That is the balance htmx aims for: rich interaction, server authority, and HTML that still describes what the page can do.
It will not make every interface simpler. A graphics editor, an offline-first workspace or an app built around a deeply collaborative local model may justify a richer client architecture. Many business applications, however, are mostly navigation, forms, tables, validation and workflows the server already owns. For those, returning the finished representation is often simpler than keeping two state machines in sync.
Key takeaways
- The htmx model is unchanged: elements are hypermedia controls, responses are HTML, and the server owns state.
- Implicit attribute inheritance is gone; missing
:inheritedsuffixes are the most likely source of silent breakage, especially for CSRF headers. - Error responses now swap by default, so every
4xxand5xxbody must be a valid fragment for its target. <hx-partial>and morphing swaps make multi-region updates and state-preserving swaps explicit and predictable.- Streaming, client-side interactivity and history caching moved into opt-in extensions, keeping the core small.
- Run the upgrade checker, then test real request paths; the checker finds candidates, and only testing proves the app still works.