# htmx upgrade guide: 1.x to 2.x and 2.x to 4.x
November 1, 2026. Companion to [htmx for small and medium businesses](https://jwatte.com/blog/htmx-small-business-guide/).
Use this kit with a developer who can test your forms, permissions and browser history. Make a branch and keep the currently deployed library, templates and configuration available for rollback. An upgrade can change security behavior even when the first button still works.
## Find what the site actually loads
Check the browser's Network panel for every htmx script. Inspect the script URL and compare the downloaded file with the version you expect. In the console, read `htmx.version`. For a bundled app, also inspect `package.json` and the lockfile; the browser may be serving an older build.
```sh
rg -n -i 'htmx|hx-ext|hx-on' templates public assets package.json package-lock.json
npm ls htmx.org
```
Replace the directory arguments with your project's directories. Search commands throughout this guide run from the application root. Searches produce candidates for review, not a complete parser of generated templates. Check runtime HTML too.
## Stay or upgrade?
| Situation | Practical decision | Reason |
|---|---|---|
| Working 2.x business application | Pin 2.0.11; schedule a tested upgrade when useful | The project promises indefinite 2.x support; the release is not an emergency migration notice |
| New application with a developer prepared for the 4.x API | Evaluate and explicitly pin 4.0.0 | It is released, with current features and a different event and security configuration model |
| Shared hosting tutorial or extension assumes 2.x | Keep 2.x until every dependency is accounted for | Loading 4.x cannot make an old extension compatible |
| 1.x site without an Internet Explorer requirement | Test 1.x to 2.x first, then 2.x to 4.x separately if wanted | Our staging recommendation isolates two sets of changes; it is not a required order stated by the official guides |
| Internet Explorer remains a contractual requirement | Review that requirement before leaving 1.x | 2.x drops IE support |
| Offline editor or extensive React/Vue application | Reassess the architecture before adding htmx | A version upgrade will not resolve a poor fit |
The [project homepage](https://htmx.org/) explains why npm `latest` remains on 2.x: to prevent accidental upgrades, with a switch planned during 2027. The [4.0.0 announcement](https://four.htmx.org/announcements/2026-08-28-htmx-4.0.0-is-released) says, “htmx 2 will continue to be supported indefinitely”. That is a maintenance statement, not a paid response time or a published end date. Our new application recommendation above is a judgment call; the pages reviewed do not mandate one release for all new projects.
Registry metadata checked November 1 lists `latest` as 2.0.11, published September 22, 2026, and `next` as 4.0.0, published August 28, 2026. There is no intervening htmx 3 release in that upgrade path. Pin an exact version instead of trusting a changing tag.
## Part 1: every change in the official 1.x to 2.x guide
The following rows cover the [official migration guide](https://htmx.org/migration-guide-htmx-1/). Retaining a former default is possible in several cases, but consider whether it is appropriate for your site.
| Old | New | What can break or needs review | Find candidates |
|---|---|---|---|
| One distribution entry assumed by a module loader | ESM `dist/htmx.esm.js`, AMD `dist/htmx.amd.js`, CJS `dist/htmx.cjs.js`; `dist/htmx.js` remains browser loadable | Bundler import or module loader assumptions | `rg -n 'htmx' package.json templates assets` |
| Extensions inside the core distribution | Separate extension distributions | Old script paths can disappear; inventory every extension | `rg -n 'hx-ext\|htmx.*/ext/' .` |
| 1.x SSE extension | Upgrade to the 2.x SSE extension; guide recommends upgrading other extensions too | The old SSE integration is not a supported substitute | `rg -n 'sse\|hx-ext' templates assets` |
| Legacy `hx-ws` and `hx-sse` | WebSocket and SSE extensions | Legacy attributes need conversion, not only a new script URL | `rg -n 'hx-ws\|hx-sse' templates` |
| Smooth scrolling default | Instant scrolling; set `htmx.config.scrollBehavior = 'smooth'` to retain the old behavior | Navigation may feel different | `rg -n 'scrollBehavior\|hx-boost\|hx-swap' .` |
| DELETE values in a form encoded body | DELETE uses URL parameters; `methodsThatUseUrlParams = ['get']` restores the former rule | Backend reads the wrong parameter collection | `rg -n 'hx-delete\|methodsThatUseUrlParams' .` |
| Cross origin requests allowed by the default | `selfRequestsOnly` defaults to true; false restores cross origin requests | External endpoints stop working; do not relax this without review | `rg -n 'selfRequestsOnly\|https?://' templates assets` |
| Multi-event `hx-on` attribute | Separate `hx-on:htmx:before-request` style attributes | Handlers must move; event names in HTML need kebab case | `rg -n 'hx-on=' templates` |
| `htmx.makeFragment()` could return an Element or DocumentFragment | Always a DocumentFragment | Custom code relying on the root element changes | `rg -n 'makeFragment' .` |
| Internal `selectAndSwap` | Public/internal `swap` method | Custom extension implementations need updating | `rg -n 'selectAndSwap' .` |
| Internet Explorer supported | No IE support in 2.x | A contractual browser requirement can prevent migration | `rg -n -i 'internet explorer\|IE11\|Trident' .` |
### Extensions now have their own install lines
These are the versions published in the official 2.x extension pages at this read. Download and verify them before production, or retain their published SRI attributes when using a CDN. This kit does not instruct you to add an extension your app does not use.
```html
```
```sh
npm install --save-exact htmx-ext-sse@2.2.4 htmx-ext-ws@2.0.4
```
For SSE, use `hx-ext="sse"` with `sse-connect` and `sse-swap`. For WebSockets, use `hx-ext="ws"` with `ws-connect` and `ws-send`. The [extension catalog](https://htmx.org/extensions/) lists the remaining packages. All extensions moved out of the core distribution; the migration guide specifically requires the SSE upgrade. Do not invent package versions for other extensions: select the exact version from that extension's official installation page and lock it.
## Part 2: 2.x to 4.x
The [official change catalog](https://four.htmx.org/docs/whats-new-in-htmx-4) calls itself exhaustive. The tables below cover its breaking changes, renames, removals and additions as read November 1. They are an inventory, not proof that third party integrations will work. Extension specific changes follow afterward.
| Old | New | What can break or needs review | Find candidates |
|---|---|---|---|
| XMLHttpRequest transport | Native `fetch()` | XHR hooks and upload progress code cannot simply be retained | `rg -n 'XMLHttpRequest\|htmx:xhr' .` |
| Implicit inherited attributes | Add `:inherited`; `:append` merges inherited values | Parent targets, CSRF headers and confirmations can stop reaching children | `rg -n 'hx-(headers\|target\|confirm\|boost\|include)' templates` |
| Ignore 4xx/5xx response bodies for swapping | Swap all responses except 204 and 304 | Error pages may replace a panel; use `hx-status` or `noSwap` | `rg -n 'responseHandling\|beforeSwap\|hx-post' .` |
| DELETE includes surrounding form fields | DELETE excludes those fields | Add `hx-include="closest form"` when required | `rg -n 'hx-delete' templates` |
| History snapshots in localStorage | Fetch again on back navigation | Restore endpoints must return full page HTML; optional history cache extension uses sessionStorage | `rg -n 'hx-push-url\|hx-replace-url\|historyCache' .` |
| OOB updates before main update | Main update first, OOB and partial updates afterward in document order | Dependent swap order can fail | `rg -n 'hx-swap-oob' templates` |
| `hx-trigger="click queue:all"` | `hx-sync="this:queue all"` | Trigger queue modifiers no longer work | `rg -n 'queue:' templates` |
| Parenthesized selectors in trigger modifiers | Quote selectors with spaces or commas using single quotes | `from:(closest form)` becomes `from:'closest form'`; review `target:` too | `rg -n 'from:\|target:' templates` |
| No request timeout | 60 second default | Slow reports time out; `defaultTimeout = 0` restores no timeout | `rg -n 'timeout\|defaultTimeout' .` |
| `hx-ext` activates an extension | Load each extension script; optional configuration allowlist | Loading old extension files or retaining activation assumptions fails | `rg -n 'hx-ext\|defineExtension' .` |
| `show:#other:top`, `scroll:#other:bottom` | `show:top showTarget:#other`, `scroll:bottom scrollTarget:#other` | Combined selector/position syntax no longer works | `rg -n 'show:\|scroll:' templates` |
| `htmx.takeClass()` | `htmx.live.take()` in the optional hx-live extension | Helper removed from core | `rg -n 'takeClass' .` |
`htmx.config.implicitInheritance = true` can restore implicit inheritance temporarily. `htmx.config.noSwap = [204, 304, '4xx', '5xx']` restores the former error response policy. A compatibility switch still needs tests and an owner to remove it later.
Rename security wrappers carefully. A wrapper protecting untrusted content can carry both `hx-disable` and `hx-ignore` during the transition, so each major release sees its own ignore attribute. After switching, remove the old `hx-disable` only after confirming the wrapper still blocks htmx processing. Convert former `hx-disabled-elt` attributes separately. The new meaning of `hx-disable` is to disable controls during a request.
### Attribute names
| Old | New | What can break or needs review | Find candidates |
|---|---|---|---|
| hx-disable | hx-ignore | Update markup; the old attribute no longer supplies the former behavior; Rename this first to avoid conflicts. | `rg -n -F 'hx-disable' .` |
| hx-disabled-elt | hx-disable | Update markup; the old attribute no longer supplies the former behavior; Rename this after hx-disable becomes hx-ignore. | `rg -n -F 'hx-disabled-elt' .` |
| hx-vars | hx-vals | Update markup; the old attribute no longer supplies the former behavior; Use the js: prefix. | `rg -n -F 'hx-vars' .` |
| hx-params | htmx:config:request | Update markup; the old attribute no longer supplies the former behavior; Configure request parameters in the event. | `rg -n -F 'hx-params' .` |
| hx-prompt | hx-prompt extension | Update markup; the old attribute no longer supplies the former behavior; You can also use a one-liner with hx-on. | `rg -n -F 'hx-prompt' .` |
| hx-ext | None | Update markup; the old attribute no longer supplies the former behavior; Include extension scripts directly. | `rg -n -F 'hx-ext' .` |
| hx-disinherit | None | Update markup; the old attribute no longer supplies the former behavior; Not needed. Inheritance is explicit. | `rg -n -F 'hx-disinherit' .` |
| hx-inherit | None | Update markup; the old attribute no longer supplies the former behavior; Not needed. Inheritance is explicit. | `rg -n -F 'hx-inherit' .` |
| hx-request | hx-config | Update markup; the old attribute no longer supplies the former behavior | `rg -n -F 'hx-request' .` |
| hx-history | None | Update markup; the old attribute no longer supplies the former behavior; No localStorage. | `rg -n -F 'hx-history' .` |
### Event names
| Old | New | What can break or needs review | Find candidates |
|---|---|---|---|
| htmx:afterOnLoad | htmx:after:init | Update listeners and check the event detail object | `rg -n -F 'htmx:afterOnLoad' .` |
| htmx:afterProcessNode | htmx:after:init | Update listeners and check the event detail object | `rg -n -F 'htmx:afterProcessNode' .` |
| htmx:afterRequest | htmx:after:request | Update listeners and check the event detail object | `rg -n -F 'htmx:afterRequest' .` |
| htmx:afterSettle | htmx:after:settle | Update listeners and check the event detail object | `rg -n -F 'htmx:afterSettle' .` |
| htmx:afterSwap | htmx:after:swap | Update listeners and check the event detail object | `rg -n -F 'htmx:afterSwap' .` |
| htmx:beforeCleanupElement | htmx:before:cleanup | Update listeners and check the event detail object | `rg -n -F 'htmx:beforeCleanupElement' .` |
| htmx:beforeHistorySave | htmx:before:history:update | Update listeners and check the event detail object | `rg -n -F 'htmx:beforeHistorySave' .` |
| htmx:beforeOnLoad | htmx:before:init | Update listeners and check the event detail object | `rg -n -F 'htmx:beforeOnLoad' .` |
| htmx:beforeProcessNode | htmx:before:process | Update listeners and check the event detail object | `rg -n -F 'htmx:beforeProcessNode' .` |
| htmx:beforeRequest | htmx:before:request | Update listeners and check the event detail object | `rg -n -F 'htmx:beforeRequest' .` |
| htmx:beforeSwap | htmx:before:swap | Update listeners and check the event detail object | `rg -n -F 'htmx:beforeSwap' .` |
| htmx:configRequest | htmx:config:request | Update listeners and check the event detail object | `rg -n -F 'htmx:configRequest' .` |
| htmx:historyCacheMiss | htmx:before:history:restore | Update listeners and check the event detail object | `rg -n -F 'htmx:historyCacheMiss' .` |
| htmx:historyRestore | htmx:before:history:restore | Update listeners and check the event detail object | `rg -n -F 'htmx:historyRestore' .` |
| htmx:load | htmx:after:init | Update listeners and check the event detail object | `rg -n -F 'htmx:load' .` |
| htmx:oobAfterSwap | htmx:after:swap | Update listeners and check the event detail object | `rg -n -F 'htmx:oobAfterSwap' .` |
| htmx:oobBeforeSwap | htmx:before:swap | Update listeners and check the event detail object | `rg -n -F 'htmx:oobBeforeSwap' .` |
| htmx:pushedIntoHistory | htmx:after:history:push | Update listeners and check the event detail object | `rg -n -F 'htmx:pushedIntoHistory' .` |
| htmx:replacedInHistory | htmx:after:history:replace | Update listeners and check the event detail object | `rg -n -F 'htmx:replacedInHistory' .` |
| htmx:responseError | htmx:response:error | Update listeners and check the event detail object; HTTP error responses use this event. | `rg -n -F 'htmx:responseError' .` |
| htmx:sendError | htmx:error | Update listeners and check the event detail object | `rg -n -F 'htmx:sendError' .` |
| htmx:swapError | htmx:error | Update listeners and check the event detail object | `rg -n -F 'htmx:swapError' .` |
| htmx:targetError | htmx:error | Update listeners and check the event detail object | `rg -n -F 'htmx:targetError' .` |
| htmx:timeout | htmx:error | Update listeners and check the event detail object | `rg -n -F 'htmx:timeout' .` |
| htmx:validation:validate | None | Update listeners and check the event detail object; Use native browser form validation. | `rg -n -F 'htmx:validation:validate' .` |
| htmx:validation:failed | None | Update listeners and check the event detail object; Use native browser form validation. | `rg -n -F 'htmx:validation:failed' .` |
| htmx:validation:halted | None | Update listeners and check the event detail object; Use native browser form validation. | `rg -n -F 'htmx:validation:halted' .` |
| htmx:xhr:loadstart | None | Update listeners and check the event detail object; htmx uses fetch() now. | `rg -n -F 'htmx:xhr:loadstart' .` |
| htmx:xhr:loadend | htmx:finally:request | Update listeners and check the event detail object; htmx uses fetch() now. | `rg -n -F 'htmx:xhr:loadend' .` |
| htmx:xhr:progress | None | Update listeners and check the event detail object; htmx uses fetch() now. | `rg -n -F 'htmx:xhr:progress' .` |
| htmx:xhr:abort | htmx:error | Update listeners and check the event detail object; htmx uses fetch() now. | `rg -n -F 'htmx:xhr:abort' .` |
### Configuration names and defaults
| Old | New | What can break or needs review | Find candidates |
|---|---|---|---|
| defaultSwapStyle | defaultSwap | Update configuration and check the effective default | `rg -n -F 'defaultSwapStyle' .` |
| globalViewTransitions | transitions | Update configuration and check the effective default | `rg -n -F 'globalViewTransitions' .` |
| historyEnabled | history | Update configuration and check the effective default | `rg -n -F 'historyEnabled' .` |
| includeIndicatorStyles | includeIndicatorCSS | Update configuration and check the effective default | `rg -n -F 'includeIndicatorStyles' .` |
| timeout | defaultTimeout | Update configuration and check the effective default | `rg -n -F 'timeout' .` |
| defaultTimeout | 60000 | Update configuration and check the effective default; Was 0 (no timeout). | `rg -n -F 'defaultTimeout' .` |
| defaultSettleDelay | 1 | Update configuration and check the effective default; Was 20. | `rg -n -F 'defaultSettleDelay' .` |
| addedClass | None | Old setting no longer controls this behavior; The htmx-added CSS class is still applied during swaps. | `rg -n -F 'addedClass' .` |
| allowEval | None | Old setting no longer controls this behavior | `rg -n -F 'allowEval' .` |
| allowNestedOobSwaps | None | Old setting no longer controls this behavior | `rg -n -F 'allowNestedOobSwaps' .` |
| allowScriptTags | None | Old setting no longer controls this behavior | `rg -n -F 'allowScriptTags' .` |
| attributesToSettle | None | Old setting no longer controls this behavior | `rg -n -F 'attributesToSettle' .` |
| defaultSwapDelay | None | Old setting no longer controls this behavior | `rg -n -F 'defaultSwapDelay' .` |
| disableSelector | hx-ignore | Old setting no longer controls this behavior | `rg -n -F 'disableSelector' .` |
| getCacheBusterParam | None | Old setting no longer controls this behavior | `rg -n -F 'getCacheBusterParam' .` |
| historyCacheSize | None | Old setting no longer controls this behavior | `rg -n -F 'historyCacheSize' .` |
| ignoreTitle | None | Old setting no longer controls this behavior; Still works per swap via hx-swap="... ignoreTitle:true". | `rg -n -F 'ignoreTitle' .` |
| inlineStyleNonce | None | Old setting no longer controls this behavior; Indicator CSS now uses Constructable Stylesheets, so no nonce is needed. | `rg -n -F 'inlineStyleNonce' .` |
| methodsThatUseUrlParams | None | Old setting no longer controls this behavior | `rg -n -F 'methodsThatUseUrlParams' .` |
| refreshOnHistoryMiss | None | Old setting no longer controls this behavior | `rg -n -F 'refreshOnHistoryMiss' .` |
| responseHandling | hx-status | Old setting no longer controls this behavior; Use with noSwap. | `rg -n -F 'responseHandling' .` |
| scrollBehavior | None | Old setting no longer controls this behavior | `rg -n -F 'scrollBehavior' .` |
| scrollIntoViewOnBoost | None | Old setting no longer controls this behavior | `rg -n -F 'scrollIntoViewOnBoost' .` |
| selfRequestsOnly | htmx.config.mode | Old setting no longer controls this behavior | `rg -n -F 'selfRequestsOnly' .` |
| settlingClass | None | Old setting no longer controls this behavior; The htmx-settling CSS class is still applied during swaps. | `rg -n -F 'settlingClass' .` |
| swappingClass | None | Old setting no longer controls this behavior; The htmx-swapping CSS class is still applied during swaps. | `rg -n -F 'swappingClass' .` |
| triggerSpecsCache | None | Old setting no longer controls this behavior | `rg -n -F 'triggerSpecsCache' .` |
| useTemplateFragments | None | Old setting no longer controls this behavior | `rg -n -F 'useTemplateFragments' .` |
| withCredentials | hx-config | Old setting no longer controls this behavior | `rg -n -F 'withCredentials' .` |
| wsBinaryType | None | Old setting no longer controls this behavior | `rg -n -F 'wsBinaryType' .` |
| wsReconnectDelay | None | Old setting no longer controls this behavior | `rg -n -F 'wsReconnectDelay' .` |
### Request headers
| Old | New | What can break or needs review | Find candidates |
|---|---|---|---|
| HX-Trigger | HX-Source | Update server header handling and any tests of the old format; Format is tagName#id, e.g. button#submit. | `rg -n -F 'HX-Trigger' .` |
| HX-Target | HX-Target | Update server header handling and any tests of the old format; Format is tagName#id. | `rg -n -F 'HX-Target' .` |
| HX-Trigger-Name | HX-Source | Update server header handling and any tests of the old format | `rg -n -F 'HX-Trigger-Name' .` |
| HX-Prompt | hx-prompt extension | Update server header handling and any tests of the old format; Load the extension. | `rg -n -F 'HX-Prompt' .` |
| None | HX-Request-Type | New metadata; update parsers only if you use it; "full" or "partial". | `rg -n -F 'HX-Request-Type' .` |
| None | Accept | New metadata; update parsers only if you use it; Core requests send text/html; hx-sse adds text/event-stream. | `rg -n -F 'Accept' .` |
### Response headers
| Old | New | What can break or needs review | Find candidates |
|---|---|---|---|
| HX-Trigger-After-Swap | HX-Trigger | Update server header handling and any tests of the old format; You can also use JavaScript. | `rg -n -F 'HX-Trigger-After-Swap' .` |
| HX-Trigger-After-Settle | HX-Trigger | Update server header handling and any tests of the old format; You can also use JavaScript. | `rg -n -F 'HX-Trigger-After-Settle' .` |
| HX-Trigger | HX-Trigger | No name change; retain the server behavior and test it | `rg -n -F 'HX-Trigger' .` |
| HX-Location | HX-Location | No name change; retain the server behavior and test it | `rg -n -F 'HX-Location' .` |
| HX-Push-Url | HX-Push-Url | No name change; retain the server behavior and test it | `rg -n -F 'HX-Push-Url' .` |
| HX-Redirect | HX-Redirect | No name change; retain the server behavior and test it | `rg -n -F 'HX-Redirect' .` |
| HX-Refresh | HX-Refresh | No name change; retain the server behavior and test it | `rg -n -F 'HX-Refresh' .` |
| HX-Replace-Url | HX-Replace-Url | No name change; retain the server behavior and test it | `rg -n -F 'HX-Replace-Url' .` |
| HX-Retarget | HX-Retarget | No name change; retain the server behavior and test it | `rg -n -F 'HX-Retarget' .` |
| HX-Reswap | HX-Reswap | No name change; retain the server behavior and test it | `rg -n -F 'HX-Reswap' .` |
| HX-Reselect | HX-Reselect | No name change; retain the server behavior and test it | `rg -n -F 'HX-Reselect' .` |
### JavaScript API
| Old | New | What can break or needs review | Find candidates |
|---|---|---|---|
| htmx.addClass() | element.classList.add() | Update custom JavaScript or extension code; Use native JavaScript. | `rg -n -F 'htmx.addClass' .` |
| htmx.removeClass() | element.classList.remove() | Update custom JavaScript or extension code; Use native JavaScript. | `rg -n -F 'htmx.removeClass' .` |
| htmx.toggleClass() | element.classList.toggle() | Update custom JavaScript or extension code; Use native JavaScript. | `rg -n -F 'htmx.toggleClass' .` |
| htmx.closest() | element.closest() | Update custom JavaScript or extension code; Use native JavaScript. | `rg -n -F 'htmx.closest' .` |
| htmx.remove() | element.remove() | Update custom JavaScript or extension code; Use native JavaScript. | `rg -n -F 'htmx.remove' .` |
| htmx.off() | removeEventListener() | Update custom JavaScript or extension code; htmx.on() returns the callback. | `rg -n -F 'htmx.off' .` |
| htmx.location() | htmx.ajax() | Update custom JavaScript or extension code | `rg -n -F 'htmx.location' .` |
| htmx.defineExtension() | htmx.registerExtension() | Update custom JavaScript or extension code | `rg -n -F 'htmx.defineExtension' .` |
| htmx.logAll() | htmx.config.logAll = true | Update custom JavaScript or extension code; htmx logs via console.*. | `rg -n -F 'htmx.logAll' .` |
| htmx.logNone() | None | Update custom JavaScript or extension code | `rg -n -F 'htmx.logNone' .` |
| htmx.logger | console.* | Update custom JavaScript or extension code; Observability tools capture console.error, console.warn, and console.log. | `rg -n -F 'htmx.logger' .` |
| htmx.onLoad() | htmx.onLoad() | Update custom JavaScript or extension code; Now listens on htmx:after:process, not htmx:after:init. | `rg -n -F 'htmx.onLoad' .` |
`HX-Request: true` is still useful for detecting htmx requests. A header is not authentication. Check the signed in user's permissions on every request. In 4.x, `HX-Request-Type` distinguishes `full` from `partial`; history and boosted navigation need deliberate server responses. The absence of a removed configuration flag does not mean the old security guarantee remains active. See the [deploy and use kit](https://jwatte.com/downloads/htmx-small-business-kit.md) for the tested CSP configuration for each version.
### New facilities listed by the change catalog
These are optional features to evaluate after the existing application passes. A new feature does not require changing working markup. The final column finds existing work that might interact with it.
| Old | New | What changes or needs review | Find candidates |
|---|---|---|---|
| Replace target DOM | `innerMorph`, `outerMorph` | Built in morphing; test widget state, focus and preserved attributes | `rg -n 'hx-swap' templates` |
| Parse response as HTML | `textContent` swap | Inserts text without parsing HTML | `rg -n 'hx-swap' templates` |
| Existing delete swap | `delete` remains available in the listed 4.x styles | Review behavior; the catalog lists this style, not evidence it first appeared in 4 | `rg -n 'delete' templates` |
| Global response policy | `hx-status:422`, `hx-status:5xx` | Per-status swap, target, select, push, replace and transition controls | `rg -n 'responseHandling\|beforeSwap' .` |
| OOB element updates | `` as another option | Explicit targets and strategies for extra swaps | `rg -n 'hx-swap-oob' templates` |
| View transition config | `transitions = true` | Still opt in; test browser support and reduced motion | `rg -n 'globalViewTransitions\|transition:' .` |
| Colon in attributes | Configurable `metaCharacter` | Helps JSX emit attribute names; review generated HTML | `rg -n 'hx-\|metaCharacter' templates assets` |
| App timer helper | `htmx.timeout()` | Promise based delay accepts milliseconds or interval text | `rg -n 'setTimeout' assets` |
| Old event detail assumptions | Consistent `ctx` request context | Rewrite consumers using the documented context shape | `rg -n 'event.detail\|evt.detail' assets` |
| Custom event/timer race helper | `htmx.live.forEvent()` | Optional hx-live helper for events and timeouts | `rg -n 'Promise.race\|addEventListener' assets` |
| Custom animation-frame promise | `htmx.live.nextFrame()` | Optional hx-live frame helper | `rg -n 'requestAnimationFrame' assets` |
| DOM selector wrapper | `htmx.live.q()` | Optional hx-live selector proxy | `rg -n 'querySelector' assets` |
| Custom debounce helper | `htmx.live.debounce()` | Optional hx-live debounce helper | `rg -n 'debounce' assets` |
| Manual reactive refresh | `htmx.live.refresh()` | Optional hx-live expression refresh | `rg -n 'hx-live' templates assets` |
| Custom logger hook | Errors and warnings route to console methods | Check monitoring captures console output and sensitive values are excluded | `rg -n 'htmx.logger\|logAll\|logNone' .` |
### New attribute catalog
| Old or previous equivalent | New | Use or review | Find candidates |
|---|---|---|---|
| Optional addition | hx-action | Specify URL, with optional hx-method. Supports progressive enhancement via native action/method fallback | `rg -n -F 'hx-action' .` |
| Optional addition | hx-method | Specify HTTP method (overrides native method and formmethod) | `rg -n -F 'hx-method' .` |
| Optional addition | hx-query | Issue a QUERY request, which sends parameters in the body | `rg -n -F 'hx-query' .` |
| Optional addition | hx-config | Per-element request config (JSON or key:value syntax) | `rg -n -F 'hx-config' .` |
| Optional addition | hx-ignore | Disable htmx processing (was hx-disable) | `rg -n -F 'hx-ignore' .` |
| Optional addition | hx-validate | Control form validation behavior | `rg -n -F 'hx-validate' .` |
### Additional swap names
| Old or previous equivalent | New | Use or review | Find candidates |
|---|---|---|---|
| beforebegin | before | Alias; former name still works | `rg -n -F 'before' .` |
| afterend | after | Alias; former name still works | `rg -n -F 'after' .` |
| afterbegin | prepend | Alias; former name still works | `rg -n -F 'prepend' .` |
| beforeend | append | Alias; former name still works | `rg -n -F 'append' .` |
### Additional event catalog
| Old or previous equivalent | New | Use or review | Find candidates |
|---|---|---|---|
| Optional addition | htmx:after:cleanup | After element cleanup | `rg -n -F 'htmx:after:cleanup' .` |
| Optional addition | htmx:after:history:update | After history update | `rg -n -F 'htmx:after:history:update' .` |
| Optional addition | htmx:after:process | After element processing | `rg -n -F 'htmx:after:process' .` |
| Optional addition | htmx:before:response | Before response body is read (cancellable) | `rg -n -F 'htmx:before:response' .` |
| Optional addition | htmx:before:settle | Before settle phase | `rg -n -F 'htmx:before:settle' .` |
| Optional addition | htmx:after:settle | After settle phase | `rg -n -F 'htmx:after:settle' .` |
| Optional addition | htmx:before:viewTransition | Before a view transition starts | `rg -n -F 'htmx:before:viewTransition' .` |
| Optional addition | htmx:after:viewTransition | After a view transition completes | `rg -n -F 'htmx:after:viewTransition' .` |
| Optional addition | htmx:finally:request | When request completes, fails, or is cancelled | `rg -n -F 'htmx:finally:request' .` |
### New configuration catalog
| Old or previous equivalent | New | Use or review | Find candidates |
|---|---|---|---|
| Optional addition | extensions | Comma-separated list of allowed extension names; default: '' | `rg -n -F 'extensions' .` |
| Optional addition | mode | Fetch mode (replaces selfRequestsOnly); default: 'same-origin' | `rg -n -F 'mode' .` |
| Optional addition | inlineScriptNonce | Nonce for inline scripts; default: '' | `rg -n -F 'inlineScriptNonce' .` |
| Optional addition | metaCharacter | Separator character in attribute/event names; default: ':' | `rg -n -F 'metaCharacter' .` |
| Optional addition | morphIgnore | Attribute name prefixes to preserve during morph; default: ["data-htmx-powered"] | `rg -n -F 'morphIgnore' .` |
| Optional addition | morphScanLimit | Max elements to scan during morph matching; default: not specified in the catalog | `rg -n -F 'morphScanLimit' .` |
| Optional addition | morphSkip | CSS selector for elements to skip during morph; default: '[hx-morph-skip]' | `rg -n -F 'morphSkip' .` |
| Optional addition | morphSkipChildren | CSS selector for elements whose children to skip during morph; default: '[hx-morph-skip-children]' | `rg -n -F 'morphSkipChildren' .` |
### Core extension catalog
| Old or previous equivalent | New | Use or review | Find candidates |
|---|---|---|---|
| Optional addition | hx-multipart | Stream HTML with multipart/mixed | `rg -n -F 'hx-multipart' .` |
| Optional addition | hx-sse | Stream HTML with text/event-stream (SSE) (upgrade guide) | `rg -n -F 'hx-sse' .` |
| Optional addition | hx-ws | Stream HTML and send data over WebSockets (upgrade guide) | `rg -n -F 'hx-ws' .` |
| Optional addition | hx-browser-indicator | Show tab’s spinner with hx-browser-indicator | `rg -n -F 'hx-browser-indicator' .` |
| Optional addition | hx-live | Our own DOM-based reactive scripting solution | `rg -n -F 'hx-live' .` |
| Optional addition | hx-pending | Show custom content during requests | `rg -n -F 'hx-pending' .` |
| Optional addition | hx-prompt | Prompt before requests with hx-prompt='Reason?' | `rg -n -F 'hx-prompt' .` |
| Optional addition | hx-preload | Preload on hover (upgrade guide) | `rg -n -F 'hx-preload' .` |
| Optional addition | hx-history-cache | Restore back/forward pages from sessionStorage | `rg -n -F 'hx-history-cache' .` |
| Optional addition | hx-ptag | Skip unchanged polls with HX-PTag: "v42" | `rg -n -F 'hx-ptag' .` |
| Optional addition | hx-download | Download files with hx-swap='download' | `rg -n -F 'hx-download' .` |
| Optional addition | hx-head | Merge tags with hx-head='merge' | `rg -n -F 'hx-head' .` |
| Optional addition | hx-targets | Target many elements with hx-targets='.selector' | `rg -n -F 'hx-targets' .` |
| Optional addition | hx-upsert | Update or insert elements with hx-swap='upsert' | `rg -n -F 'hx-upsert' .` |
| Optional addition | htmx-2-compat | Restore htmx 2.x defaults and event names on htmx 4 | `rg -n -F 'htmx-2-compat' .` |
| Optional addition | hx-alpine-compat | Run htmx alongside Alpine.js without conflicts | `rg -n -F 'hx-alpine-compat' .` |
| Optional addition | hx-csp | Make htmx work under strict Content Security Policy | `rg -n -F 'hx-csp' .` |
The catalog includes some names already known in earlier releases among its feature lists. Treat these as the project's 4.x inventory, not a claim that every item was invented in 4.0.0. Do not remove security or request settings solely because this table groups them under additions.
### SSE, WebSocket and preload extension migration
The core guide links to the separate extension guides. Their 2.x migration sections add the following changes. Beta to release candidate transitions are outside this 2.x upgrade; prerelease users must also read those sections.
```html
```
Those are versioned package paths. Self host the exact files after integrity verification as described in the deployment kit. Load only the extensions needed by that page. The installation lines show their location, not a recommendation to enable all three.
| Old | New | What can break or needs review | Find candidates |
|---|---|---|---|
| SSE uses EventSource | Fetch and ReadableStream | Request headers and methods now follow normal htmx requests | `rg -n 'EventSource\|sse-' .` |
| Named SSE messages select a swap via `sse-swap` | Unnamed messages swap; named messages dispatch DOM events | Change the server's event stream or handle named events explicitly | `rg -n 'sse-swap\|event:' .` |
| `hx-trigger="sse:progress"` | `hx-trigger="progress from:body"` | Old prefixed event will not trigger the update | `rg -n 'sse:' templates` |
| WebSocket incoming elements implicitly OOB | Connection `hx-target` and `hx-swap` control the main update | Use explicit OOB or partial elements for additional updates | `rg -n 'ws-connect\|hx-swap-oob' .` |
| WebSocket outgoing `HEADERS` field | Lowercase reserved `headers`; values stay at top level | Update server JSON parsing | `rg -n 'HEADERS\|ws-send' .` |
| `preload` | `hx-preload` | Rename attribute | `rg -n 'preload' templates` |
| Preload inherited automatically | `hx-preload:inherited` | Parent preload settings need the explicit suffix | `rg -n 'preload' templates` |
### SSE names
| Old | New | What can break or needs review | Find candidates |
|---|---|---|---|
| sse-connect | hx-sse:connect | Update markup; the old attribute no longer supplies the former behavior | `rg -n -F 'sse-connect' .` |
| sse-swap | Unnamed messages swap automatically | Update markup; the old attribute no longer supplies the former behavior | `rg -n -F 'sse-swap' .` |
| sse-close | hx-sse:close | Update markup; the old attribute no longer supplies the former behavior | `rg -n -F 'sse-close' .` |
### SSE names and events
| Old | New | What can break or needs review | Find candidates |
|---|---|---|---|
| htmx:sseOpen | htmx:sse:after:connection | Update listeners and check the event detail object | `rg -n -F 'htmx:sseOpen' .` |
| htmx:sseError | htmx:sse:error | Update listeners and check the event detail object | `rg -n -F 'htmx:sseError' .` |
| htmx:sseBeforeMessage | htmx:sse:before:message | Update listeners and check the event detail object | `rg -n -F 'htmx:sseBeforeMessage' .` |
| htmx:sseMessage | htmx:sse:after:message | Update listeners and check the event detail object | `rg -n -F 'htmx:sseMessage' .` |
| htmx:sseClose | htmx:sse:close | Update listeners and check the event detail object | `rg -n -F 'htmx:sseClose' .` |
### WebSocket names
| Old | New | What can break or needs review | Find candidates |
|---|---|---|---|
| ws-connect | hx-ws:connect | Update markup; the old attribute no longer supplies the former behavior | `rg -n -F 'ws-connect' .` |
| ws-send | hx-ws:send | Update markup; the old attribute no longer supplies the former behavior | `rg -n -F 'ws-send' .` |
| htmx.config.wsReconnectDelay | htmx.config.ws.reconnectDelay | Update markup; the old attribute no longer supplies the former behavior | `rg -n -F 'htmx.config.wsReconnectDelay' .` |
| createWebSocket | Removed | Update markup; the old attribute no longer supplies the former behavior | `rg -n -F 'createWebSocket' .` |
| wsBinaryType | Removed | Update markup; the old attribute no longer supplies the former behavior | `rg -n -F 'wsBinaryType' .` |
| socketWrapper | Removed | Update markup; the old attribute no longer supplies the former behavior | `rg -n -F 'socketWrapper' .` |
### WebSocket names and events
| Old | New | What can break or needs review | Find candidates |
|---|---|---|---|
| htmx:wsConnecting | Removed | Update listeners and check the event detail object | `rg -n -F 'htmx:wsConnecting' .` |
| htmx:wsOpen | htmx:ws:after:connection | Update listeners and check the event detail object | `rg -n -F 'htmx:wsOpen' .` |
| htmx:wsClose | htmx:ws:close | Update listeners and check the event detail object | `rg -n -F 'htmx:wsClose' .` |
| htmx:wsError | htmx:ws:error | Update listeners and check the event detail object | `rg -n -F 'htmx:wsError' .` |
| htmx:wsBeforeMessage | htmx:ws:before:message:incoming | Update listeners and check the event detail object | `rg -n -F 'htmx:wsBeforeMessage' .` |
| htmx:wsAfterMessage | htmx:ws:after:message:incoming | Update listeners and check the event detail object | `rg -n -F 'htmx:wsAfterMessage' .` |
| htmx:wsConfigSend | htmx:ws:before:message:outgoing | Update listeners and check the event detail object | `rg -n -F 'htmx:wsConfigSend' .` |
| htmx:wsBeforeSend | htmx:ws:before:message:outgoing | Update listeners and check the event detail object | `rg -n -F 'htmx:wsBeforeSend' .` |
| htmx:wsAfterSend | htmx:ws:after:message:outgoing | Update listeners and check the event detail object | `rg -n -F 'htmx:wsAfterSend' .` |
The optional [htmx-2-compat extension](https://four.htmx.org/extensions/htmx-2-compat) restores many former defaults and event names for a gradual migration. It is a temporary aid, not proof that custom code works. Load its versioned script after core if you choose that path:
```html
```
## Stage the change
1. Inventory script tags, lockfiles, extensions, event listeners, response headers, custom error policies and any place HTML contains user supplied content. Record the working deployed version.
2. Branch. Upgrade the library and templates together in a private test environment. Keep exact dependency versions and checksum records. For 1.x, first finish the 2.x test pass; this is our operational choice rather than a project mandate.
3. Run the official checker, then review its output. It is a search aid, not a security audit:
```sh
npx htmx.org@4.0.0 upgrade-check -- ./templates
```
4. Exercise one canary page with real staff, a defined observation period and a ready rollback. Select a page that can be isolated without loading two versions in one document.
5. Expand page by page. Retest shared layouts because inherited attributes and event handlers can affect screens you did not edit directly.
## Acceptance test plan
| Area | Test | Required result |
|---|---|---|
| Version | Inspect loaded file and `htmx.version` after cache refresh | Exactly the pinned version, only one core library |
| Search | Type quickly, clear input, paste text, use a slow response | Latest intended query wins; keyboard focus stays usable |
| Form | Submit valid data, missing required fields and server validation errors | Correct section updates; errors are readable and values preserved |
| Authentication | Expired session and another user's record | Sign in flow works; unauthorized data never appears |
| CSRF | Valid, absent and incorrect tokens | Valid save succeeds; invalid requests fail without changing records |
| Delete | Cancel then confirm; retry after deletion | Cancel sends no deletion; authorization checked; UI matches server |
| Server errors | 403, 404, 422 and 500 HTML; network failure | Version appropriate error handling without dumping a full error page into a small panel |
| History | Navigate, reload, copy URL, back and forward, after cache clear | Full page works directly; correct restored state and scroll |
| Security | Untrusted text containing markup; strict CSP | Text remains escaped; no CSP violations in recommended examples |
| Extensions | SSE reconnect, WebSocket messages, preload if installed | Correct payloads and events; no duplicate connections |
| Accessibility | Keyboard, screen reader and zoom | Labels, announced changes, sensible focus and usable controls |
| Operations | Monitor errors and request volume | Named owner sees failures; no unexpected polling loop |
The companion deploy kit reports the limited browser pattern tests run for this article. They do not certify your authentication, database logic or third party widgets. Run this plan against your application.
## Rollback
Keep the former asset and matching templates/configuration as one releasable unit. Use versioned filenames so browsers can distinguish releases. Avoid database schema changes in the same canary; if unavoidable, keep both versions compatible until rollout finishes. Roll back when save/delete behavior, permissions, CSRF, error handling or navigation fails. Restore the former unit, clear or expire affected cache entries, verify the loaded version, then repeat a real read and a reversible test save. Record what failed before attempting the migration again.
## Sources
Read November 1, 2026, 17:54 to 18:01 UTC unless noted. Registry release dates are package publication dates, not GitHub timestamps.
1. [htmx homepage and npm tag explanation](https://htmx.org/).
2. [2.x documentation](https://htmx.org/docs/).
3. [Official 1.x to 2.x migration guide](https://htmx.org/migration-guide-htmx-1/).
4. [4.0.0 release announcement and support promise](https://four.htmx.org/announcements/2026-08-28-htmx-4.0.0-is-released).
5. [Official 4.x change catalog](https://four.htmx.org/docs/whats-new-in-htmx-4).
6. [GitHub releases](https://github.com/bigskysoftware/htmx/releases) and [4.0.0 release](https://github.com/bigskysoftware/htmx/releases/tag/v4.0.0).
7. [Project README at 4.0.0](https://github.com/bigskysoftware/htmx/blob/v4.0.0/README.md).
8. [npm registry metadata](https://registry.npmjs.org/htmx.org).
9. [2.x extension catalog](https://htmx.org/extensions/), [SSE](https://htmx.org/extensions/sse/) and [WebSockets](https://htmx.org/extensions/ws/).
10. [4.x SSE migration](https://four.htmx.org/extensions/hx-sse#upgrading-from-htmx-2x), [WebSocket migration](https://four.htmx.org/extensions/hx-ws#upgrading-from-htmx-2x) and [preload migration](https://four.htmx.org/extensions/hx-preload#upgrading-from-htmx-2x).
11. [Compatibility extension](https://four.htmx.org/extensions/htmx-2-compat).