htmx 2.x → htmx 4.x Migration Guide
The purpose of this guide is to provide instructions for migrations from htmx 2.x to htmx 4.x.
htmx 4 is a significant architectural rewrite which involves breaking changes. We have tried to maintain backwards compatibility where possible but this upgrade will require more work than the htmx 1 to htmx 2 migration.
Biggest Changes
The three most impactful changes in htmx 4 are:
- Switching to
fetch()for issuing ajax requests - Making attribute inheritance explicit by default
- Adopting the response targets extension concept for retargeting based on
HTTP response codes, and making most HTTP response codes swap content (including
4xxand5xxresponse codes)
While there is no way to “undo” the first item in htmx 4, the second two changes can be undone by:
- Setting
htmx.config.implicitInheritancetotrue, which will restore implicit attribute inheritance - Setting
htmx.config.noSwapto[204, 304, '4xx', '5xx']
Making these to changes will make many htmx 2-based applications work with htmx 4 without further changes.
Attribute Changes
Renamed Attributes
| htmx 2.x | htmx 4.x | Notes |
|---|---|---|
hx-disabled-elt | hx-disable | Before upgrading, audit usage of hx-disable attribute (see note below) |
Important Note on hx-disable:
In htmx 2, hx-disable disables htmx processing. In htmx 4, hx-ignore serves this purpose. Before upgrading:
- Search for any usage of
hx-disablein your htmx 2 codebase - Rename
hx-disable→hx-ignore - Then rename
hx-disabled-elt→hx-disable
Removed Attributes
The following attributes have been removed:
| Removed Attribute | htmx 4 Alternative |
|---|---|
hx-vars | Use hx-vals with js: prefix |
hx-params | Use htmx:config:request event to filter parameters |
hx-prompt | Use hx-confirm with async JavaScript function |
hx-ext | Extensions now work via event listeners |
hx-disinherit | No longer needed (inheritance is explicit) |
hx-inherit | No longer needed (inheritance is explicit) |
hx-request | Use hx-config |
hx-history | Removed (history is no longer stored in local storage) |
hx-history-elt | Removed (history uses target element) |
New Attributes
| Attribute | Purpose |
|---|---|
hx-action | Specifies URL (use with hx-method) |
hx-method | Specifies HTTP method (use with hx-action) |
hx-config | Configure request behavior with JSON |
Attribute Inheritance Changes
Inheritance is now, by default, explicit using the :inherited modifier.
Before (htmx 2):
<!-- Attributes inherited automatically -->
<div hx-confirm="Are you sure?">
<button hx-delete="/item/1">Delete 1</button>
<button hx-delete="/item/2">Delete 2</button>
</div>
After (htmx 4):
<!-- Must use :inherited modifier -->
<div hx-confirm:inherited="Are you sure?">
<button hx-delete="/item/1">Delete 1</button>
<button hx-delete="/item/2">Delete 2</button>
</div>
Any attribute can use the :inherited modifier: hx-boost:inherited, hx-headers:inherited, hx-target:inherited, etc.
As mentioned above, you can revert this behavior by setting htmx.config.implicitInheritance to true
GET/DELETE Form Data
In htmx 4 hx-delete, like hx-get, no longer includes the enclosing form’s inputs by
default. Use hx-include="closest form" if you need this behavior.
Event Name Changes
htmx 4 uses a new event naming convention: htmx:phase:action[:sub-action], and so if you are using htmx events you
need to rename the events that they are listening for. Here is a complete table with the htmx 4 equivalent events:
| htmx 2.x Event | htmx 4.x Event | Notes |
|---|---|---|
htmx:afterOnLoad | htmx:after:init | |
htmx:afterProcessNode | htmx:after:init | |
htmx:afterRequest | htmx:after:request | |
htmx:afterSettle | htmx:after:swap | |
htmx:afterSwap | htmx:after:swap | |
htmx:beforeCleanupElement | htmx:before:cleanup | |
htmx:beforeHistorySave | htmx:before:history:update | |
htmx:beforeHistoryUpdate | htmx:before:history:update | |
htmx:beforeOnLoad | htmx:before:init | |
htmx:beforeProcessNode | htmx:before:process | |
htmx:beforeRequest | htmx:before:request | |
htmx:beforeSend | htmx:before:request | |
htmx:beforeSwap | htmx:before:swap | |
htmx:beforeTransition | htmx:before:viewTransition | |
htmx:configRequest | htmx:config:request | |
htmx:historyCacheMiss | htmx:before:restore:history | |
htmx:historyRestore | htmx:before:restore:history | |
htmx:load | htmx:after:init | |
htmx:oobAfterSwap | htmx:after:swap | No separate OOB swap events |
htmx:oobBeforeSwap | htmx:before:swap | No separate OOB swap events |
htmx:pushedIntoHistory | htmx:after:push:into:history | |
htmx:replacedInHistory | htmx:after:replace:into:history | |
htmx:responseError | htmx:error | All errors consolidated |
htmx:sendError | htmx:error | All errors consolidated |
htmx:sendAbort | htmx:error | All errors consolidated |
htmx:swapError | htmx:error | All errors consolidated |
htmx:targetError | htmx:error | All errors consolidated |
htmx:timeout | htmx:error | All errors consolidated |
htmx:validation:validate | Removed | Use native form validation |
htmx:validation:failed | Removed | Use native form validation |
htmx:validation:halted | Removed | Use native form validation |
htmx:xhr:abort | Removed | Use htmx:error event |
htmx:xhr:loadstart | Removed | No fetch() equivalent |
htmx:xhr:loadend | Removed | Use htmx:finally:request |
htmx:xhr:progress | Removed | Use fetch() streams API if needed |
XHR Upload Progress Events Removed
In htmx 2.x, the following XHR upload progress events were available:
htmx:xhr:loadstarthtmx:xhr:loadendhtmx:xhr:progresshtmx:xhr:abort
These events provided detailed upload progress information with lengthComputable, loaded, and total properties.
In htmx 4.x these events have been removed because htmx now uses the fetch() API instead of XMLHttpRequest.
If you need upload progress tracking in htmx 4:
- Use the
htmx:config:requestevent to access the request context - Implement custom fetch with progress tracking using fetch streams or a library
- Consider using a specialized upload library for complex upload scenarios
New Events in htmx 4
htmx:after:cleanup- Triggered after element cleanuphtmx:after:history:update- Triggered after history is updatedhtmx:after:process- Triggered after processing an elementhtmx:before:settle- Triggered before settle phase beginshtmx:after:settle- Triggered after settle phase completeshtmx:after:viewTransition- Triggered after view transition completeshtmx:after:sse:message- Triggered after processing an SSE messagehtmx:after:sse:stream- Triggered after an SSE stream endshtmx:before:sse:message- Triggered before processing an SSE messagehtmx:before:sse:reconnect- Triggered before reconnecting to SSE streamhtmx:before:sse:stream- Triggered before processing an SSE streamhtmx:finally:request- Always triggered after request completes (success or error)
JavaScript API Changes
The htmx JavaScript API has changed significantly in htmx 4.
Removed API Methods
The following JavaScript API methods have been removed in htmx 4:
| htmx 2.x Method | htmx 4 Alternative |
|---|---|
htmx.addClass() | Use native element.classList.add() |
htmx.closest() | Use native element.closest() |
htmx.location() | Use htmx.ajax() instead |
htmx.logAll() | Set htmx.config.logAll = true |
htmx.logNone() | Set htmx.config.logAll = false |
htmx.logger | Use browser DevTools or custom event listeners |
htmx.off() | Use native removeEventListener() |
htmx.remove() | Use native element.remove() |
htmx.removeClass() | Use native element.classList.remove() |
htmx.removeExtension() | Extensions are now event-based, no removal needed |
htmx.toggleClass() | Use native element.classList.toggle() |
Retained API Methods
These methods continue to exist in htmx 4:
htmx.ajax(verb, path, context)- Issue AJAX requests programmaticallyhtmx.config- Configuration object (with new options)htmx.registerExtension(name, extension)- Register extensionshtmx.find(selector)- Find elements (supports extended selectors)htmx.findAll(selector)- Find all matching elements (supports extended selectors)htmx.onLoad(callback)- a callback that will be called with the newly added content on every swap by htmxhtmx.on(eventName, handler)- Add event listenerhtmx.parseInterval(str)- Parse interval strings like “1s”, “500ms”htmx.process(element)- Process htmx attributes on an elementhtmx.swap()- Swap content into the DOMhtmx.takeClass(element, className, container)- Take a class from sibling elementshtmx.trigger(element, eventName, detail)- Trigger custom events
New API Methods
htmx.forEvent(eventName, timeout)- Returns a promise that resolves when an event fireshtmx.timeout(time)- Returns a promise that resolves after specified time
Extension API Changes
Extensions in htmx 4 use a new event-based hook system instead of the callback-based API. The method name has also changed from defineExtension() to registerExtension() to avoid conflicts with htmx 2.x.
Key changes:
- Method renamed:
htmx.defineExtension()→htmx.registerExtension() - Event-based hooks instead of callback methods
- Hook names use underscores:
htmx_before_requestinstead ofonEvent - Extensions must be approved via config
- Full request context available via
detail.ctx
Extensions will almost certainly need a rewrite. Please see our Extensions documentation and Extension Migration Guide for more information.
HTTP Header Changes
htmx 4 makes some changes to the HTTP headers sent with requests and the response headers it processes. If your server-side code relies on htmx headers, you will need to update it.
Changed Request Headers
| htmx 2.x Header | htmx 4.x Header | Notes |
|---|---|---|
HX-Trigger | HX-Source | Now uses element identifier format: tagName#id (e.g., button#submit) instead of just the element’s ID. This is the replacement for HX-Trigger. |
HX-Target | HX-Target | Still present but now uses element identifier format: tagName#id instead of just the element’s ID |
HX-Source is the direct replacement for HX-Trigger. If your server was using HX-Trigger to identify which element
initiated the request, use HX-Source in htmx 4. Note that the format has changed from just an ID to tagName#id.
Removed Request Headers
The following request headers have been removed in htmx 4:
| Removed Header | Notes |
|---|---|
HX-Trigger-Name | Previously sent the name attribute of the triggering element. Use HX-Source instead. |
HX-Prompt | Previously sent the user’s response to hx-prompt. Use hx-confirm with async JavaScript instead (see attribute changes above). |
New Request Headers
| Header | Description |
|---|---|
HX-Request-Type | Set to "full" for full page requests (target is body or has hx-select) or "partial" for partial page requests |
Accept | Now explicitly set to "text/html, text/event-stream" |
Removed Response Headers
The following response headers are no longer processed in htmx 4:
| Removed Header | Notes |
|---|---|
HX-Trigger-After-Swap | Use HX-Trigger or custom javascript instead. Timing-specific triggers are no longer supported. |
HX-Trigger-After-Settle | Use HX-Trigger or custom javascript instead. Timing-specific triggers are no longer supported. |
Unchanged Response Headers
The following response headers continue to work the same in htmx 4:
HX-Trigger- Triggers events immediately on responseHX-Location- Client-side redirectHX-Push-Url- Pushes a URL into historyHX-Redirect- Full page redirectHX-Refresh- Refresh the current pageHX-Replace-Url- Replaces the current URL in historyHX-Retarget- Change the target elementHX-Reswap- Change the swap strategyHX-Reselect- Change the content selector
Upgrade Music
This is the official htmx 2.x -> 4.x upgrade music: