</> htmx
🚧 htmx 4.0 is under construction. Read changes →

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.

Table of Contents

Biggest Changes

The three most impactful changes in htmx 4 are:

While there is no way to “undo” the first item in htmx 4, the second two changes can be undone by:

Making these to changes will make many htmx 2-based applications work with htmx 4 without further changes.

Attribute Changes

Renamed Attributes

htmx 2.xhtmx 4.xNotes
hx-disabled-elthx-disableBefore 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:

  1. Search for any usage of hx-disable in your htmx 2 codebase
  2. Rename hx-disablehx-ignore
  3. Then rename hx-disabled-elthx-disable

Removed Attributes

The following attributes have been removed:

Removed Attributehtmx 4 Alternative
hx-varsUse hx-vals with js: prefix
hx-paramsUse htmx:config:request event to filter parameters
hx-promptUse hx-confirm with js: prefix (e.g. hx-confirm="js:myAsyncFn()")
hx-extExtensions now work via event listeners
hx-disinheritNo longer needed (inheritance is explicit)
hx-inheritNo longer needed (inheritance is explicit)
hx-requestUse hx-config
hx-historyRemoved (history is no longer stored in local storage)
hx-history-eltRemoved (history uses target element)

New Attributes

AttributePurpose
hx-actionSpecifies URL (use with hx-method)
hx-methodSpecifies HTTP method (use with hx-action)
hx-configConfigure 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.

Out-of-Band Swap Order

In htmx 2, out-of-band (hx-swap-oob) elements were swapped before the main content. In htmx 4, the main content is swapped first, and OOB/hx-partial elements are swapped after. This is generally more intuitive but may affect code that relied on the previous ordering.


Configuration Changes

If you customize htmx.config, you will need to update your configuration for htmx 4.

Renamed Config Keys

htmx 2.xhtmx 4.xNotes
defaultSwapStyledefaultSwapDefault: "innerHTML"
globalViewTransitionstransitionsDefault: false
historyEnabledhistoryDefault: true
includeIndicatorStylesincludeIndicatorCSSDefault: true
timeoutdefaultTimeoutDefault changed (see below)

Changed Defaults

Confightmx 2 defaulthtmx 4 defaultImpact
defaultTimeout0 (no timeout)60000 (60 seconds)Requests that previously had no timeout will now timeout after 60 seconds
defaultSettleDelay201Settle phase is shorter

Removed Config Keys

The following configuration keys have been removed in htmx 4:

Removed KeyNotes
addedClassHardcoded to "htmx-added"
allowEvalRemoved
allowNestedOobSwapsRemoved
allowScriptTagsRemoved
attributesToSettleRemoved
defaultSwapDelayRemoved
disableSelectorUse hx-ignore instead
getCacheBusterParamRemoved
historyCacheSizeHistory no longer uses localStorage
ignoreTitleUse ignoreTitle swap modifier instead
methodsThatUseUrlParamsGET and DELETE use URL params
refreshOnHistoryMissHistory always does a full page request
responseHandlingUse hx-status and htmx.config.noSwap instead
scrollBehaviorRemoved
scrollIntoViewOnBoostRemoved
selfRequestsOnlyUse htmx.config.mode ('same-origin' by default)
settlingClassHardcoded to "htmx-settling"
swappingClassHardcoded to "htmx-swapping"
triggerSpecsCacheRemoved
useTemplateFragmentsRemoved
withCredentialsUse hx-config to set fetch options
wsBinaryTypeWebSocket extension handles this
wsReconnectDelayUse htmx.config.websockets instead

data-hx-* Attribute Prefix

In htmx 2, both hx-* and data-hx-* attributes were recognized automatically. In htmx 4, only hx-* is recognized by default. If you use data-hx-* attributes, set the prefix in your config:

<meta name="htmx-config" content='{"prefix": "data-hx-"}'>

Note: When using a custom prefix, all htmx attributes must use that prefix.


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 Eventhtmx 4.x EventNotes
htmx:afterOnLoadhtmx:after:init
htmx:afterProcessNodehtmx:after:init
htmx:afterRequesthtmx:after:request
htmx:afterSettlehtmx:after:swap
htmx:afterSwaphtmx:after:swap
htmx:beforeCleanupElementhtmx:before:cleanup
htmx:beforeHistorySavehtmx:before:history:update
htmx:beforeHistoryUpdatehtmx:before:history:update
htmx:beforeOnLoadhtmx:before:init
htmx:beforeProcessNodehtmx:before:process
htmx:beforeRequesthtmx:before:request
htmx:beforeSendhtmx:before:request
htmx:beforeSwaphtmx:before:swap
htmx:beforeTransitionhtmx:before:viewTransition
htmx:configRequesthtmx:config:request
htmx:historyCacheMisshtmx:before:restore:history
htmx:historyRestorehtmx:before:restore:history
htmx:loadhtmx:after:init
htmx:oobAfterSwaphtmx:after:swapNo separate OOB swap events
htmx:oobBeforeSwaphtmx:before:swapNo separate OOB swap events
htmx:pushedIntoHistoryhtmx:after:push:into:history
htmx:replacedInHistoryhtmx:after:replace:into:history
htmx:responseErrorhtmx:errorAll errors consolidated
htmx:sendErrorhtmx:errorAll errors consolidated
htmx:sendAborthtmx:errorAll errors consolidated
htmx:swapErrorhtmx:errorAll errors consolidated
htmx:targetErrorhtmx:errorAll errors consolidated
htmx:timeouthtmx:errorAll errors consolidated
htmx:validation:validateRemovedUse native form validation
htmx:validation:failedRemovedUse native form validation
htmx:validation:haltedRemovedUse native form validation
htmx:xhr:abortRemovedUse htmx:error event
htmx:xhr:loadstartRemovedNo fetch() equivalent
htmx:xhr:loadendRemovedUse htmx:finally:request
htmx:xhr:progressRemovedUse fetch() streams API if needed

XHR Upload Progress Events Removed

In htmx 2.x, the following XHR upload progress events were available:

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:

  1. Use the htmx:config:request event to access the request context
  2. Implement custom fetch with progress tracking using fetch streams or a library
  3. Consider using a specialized upload library for complex upload scenarios

New Events in htmx 4


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 Methodhtmx 4 Alternative
htmx.addClass()Use native element.classList.add()
htmx.closest()Use native element.closest()
htmx.createEventSourceSSE extension handles this
htmx.createWebSocketWebSocket extension handles this
htmx.defineExtension()Use htmx.registerExtension() (renamed)
htmx.location()Use htmx.ajax() instead
htmx.logAll()Set htmx.config.logAll = true
htmx.logNone()Set htmx.config.logAll = false
htmx.loggerUse 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()
htmx.values()Use native FormData or collect values manually

Retained API Methods

These methods continue to exist in htmx 4:

New API Methods

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:

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 Headerhtmx 4.x HeaderNotes
HX-TriggerHX-SourceNow 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-TargetHX-TargetStill 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 HeaderNotes
HX-Trigger-NamePreviously sent the name attribute of the triggering element. Use HX-Source instead.
HX-PromptPreviously sent the user’s response to hx-prompt. Use hx-confirm with async JavaScript instead (see attribute changes above).

New Request Headers

HeaderDescription
HX-Request-TypeSet to "full" for full page requests (target is body or has hx-select) or "partial" for partial page requests
AcceptNow explicitly set to "text/html"

Removed Response Headers

The following response headers are no longer processed in htmx 4:

Removed HeaderNotes
HX-Trigger-After-SwapUse HX-Trigger or custom javascript instead. Timing-specific triggers are no longer supported.
HX-Trigger-After-SettleUse 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:


Extension Changes

Loading Extensions

htmx 4 no longer uses the hx-ext attribute. Extensions are activated by including the script file:

<script src="/path/to/htmx.js"></script>
<script src="/path/to/ext/hx-sse.js"></script>

To restrict which extensions can register, use the extensions config as a whitelist:

<meta name="htmx-config" content='{"extensions": "sse, ws"}'>

Server-Sent Events (SSE)

The SSE extension has been rewritten for htmx 4. It now uses fetch() instead of EventSource, hooks into htmx’s standard request pipeline, and replaces sse-connect/sse-swap with hx-sse:connect.

See the SSE extension upgrade guide for full details.

WebSockets (WS)

The WebSocket extension has been rewritten for htmx 4 with a new JSON envelope message format, reference-counted connections, and renamed attributes (ws-connecthx-ws:connect).

See the WebSocket extension upgrade guide for full details.

Custom Extensions

Extensions in htmx 4 use an event-based hook system instead of the callback-based API. See the Extension Migration Guide for details on rewriting extensions.


Upgrade Music

This is the official htmx 2.x -> 4.x upgrade music: