## Get Started ### [Installation](https://four.htmx.org/docs/get-started/installation) import { Code } from 'astro:components'; import integrity from '../../../data/integrity.json'; htmx is a single JavaScript file with no dependencies. No build step is required to use it. #### CDN Add this in your `` tag: `} /> ##### Unminified `} /> ##### ES Module `} /> ##### ES Module (unminified) `} /> #### Download Instead of using a CDN, consider [self-hosting in production](https://blog.wesleyac.com/posts/why-not-javascript-cdn). 1. Download htmx.min.js 2. Save it to your project (e.g., `/js/htmx.min.js`) 3. Add this in your `` tag: ```html ``` ##### Other formats Download: htmx.js (unminified) Download: htmx.esm.min.js (ES module) Download: htmx.esm.js (ES module, unminified) #### npm ```sh npm install htmx.org@4.0.0-beta4 ``` ```javascript import 'htmx.org'; ``` ##### Named import ```javascript import htmx from 'htmx.org'; // Now you can use htmx.ajax(), htmx.find(), etc. ``` #### htmax The `htmax.js` file bundles htmx with the most popular extensions in a single file: * [SSE](https://four.htmx.org/extensions/hx-sse) * [WebSockets](https://four.htmx.org/extensions/hx-ws) * [preload](https://four.htmx.org/extensions/hx-preload) * [browser-indicator](https://four.htmx.org/extensions/hx-browser-indicator) * [download](https://four.htmx.org/extensions/hx-download) * [optimistic](https://four.htmx.org/extensions/hx-optimistic) * [targets](https://four.htmx.org/extensions/hx-targets) * [live](https://four.htmx.org/extensions/hx-live). The extensions are automatically available, you can just use their attributes directly (e.g. `hx-sse:connect`, `hx-ws:connect`). ```html ``` ### [Migration](https://four.htmx.org/docs/get-started/migration) #### Quick Start There are two major behavioral changes between htmx 2.x and 4.x: * In htmx 2.0 attribute inheritance is *implicit* by default while in 4.0 it is explicit by default * In htmx 2.0, `400` and `500` response codes are not swapped by default, whereas in htmx 4.0 these requests will be swapped Add these two config lines to restore htmx 2.x behavior: ```html ``` [`implicitInheritance`](https://four.htmx.org/reference/config/htmx-config-implicitInheritance) restores htmx 2's implicit attribute inheritance. [`noSwap`](https://four.htmx.org/reference/config/htmx-config-noSwap) prevents swapping error responses. Or load the [`htmx-2-compat`](https://four.htmx.org/extensions/htmx-2-compat) extension, which restores implicit inheritance, old event names, and previous error-swapping defaults: ```html ``` Most htmx 2 apps should work with either approach. Then migrate incrementally using this guide. #### Upgrade Checker htmx 4 ships with a command-line tool scans your templates and JS files for htmx 2 code that needs updating. It checks for removed attributes, old event names, inheritance patterns, extension changes, etc. ```bash npx htmx.org@next upgrade-check -- ./path/to/project/root npx htmx.org@next upgrade-check --ext .vue ./path/to/project/root ``` By default, the tool scans `.html`, `.php`, `.js`, `.ts`, `.jinja`, `.jinja2`, `.j2`, `.erb`, and `.hbs` files. Output is `file:line` format, clickable in most editors. You can add additional file types with the `--ext` option. The tool requires Python 3. #### What Changed ##### `fetch()` replaces `XMLHttpRequest` All requests use the native [`fetch()` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). This cannot be reverted. ##### Explicit inheritance Add [`:inherited`](https://four.htmx.org/docs/features/attribute-inheritance) to any attribute that should inherit down the DOM tree. ```html
``` Works on any attribute: [`hx-boost`](https://four.htmx.org/reference/attributes/hx-boost)`:inherited`, [ `hx-target`](https://four.htmx.org/reference/attributes/hx-target)`:inherited`, [`hx-confirm`](https://four.htmx.org/reference/attributes/hx-confirm)`:inherited`, etc. Use `:append` to add to an inherited value instead of replacing it: ```html
...
``` Revert: [`htmx.config.implicitInheritance`](https://four.htmx.org/reference/config/htmx-config-implicitInheritance) `= true` ##### Error responses swap htmx 4 swaps all HTTP responses. Only [`204`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/204) and [`304`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/304) do not swap. htmx 2 did not swap `4xx` and `5xx` responses. In htmx 4, if your server returns HTML with a `422` or `500`, that HTML gets swapped into the target. Design your error responses to work as swap content, or use [ `hx-status`](https://four.htmx.org/reference/attributes/hx-status) to control per-code behavior. Revert: [`htmx.config.noSwap`](https://four.htmx.org/reference/config/htmx-config-noSwap) `= [204, 304, '4xx', '5xx']` ##### [`hx-delete`](https://four.htmx.org/reference/attributes/hx-delete) excludes form data Like [`hx-get`](https://four.htmx.org/reference/attributes/hx-get), [`hx-delete`](https://four.htmx.org/reference/attributes/hx-delete) no longer includes the enclosing form's inputs. Fix: add [`hx-include`](https://four.htmx.org/reference/attributes/hx-include)`="closest form"` where needed. ##### No history cache History no longer caches pages in [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). When navigating back, htmx re-fetches the page and swaps it into ``, or into the `[hx-history-elt]` element if one is present — the same behavior as htmx 2. Use [`htmx.config.history`](https://four.htmx.org/reference/config/htmx-config-history) `= "reload"` for a full page reload instead. Use `htmx.config.history = false` to disable. ##### OOB swap order In htmx 2, out-of-band ([`hx-swap-oob`](https://four.htmx.org/reference/attributes/hx-swap-oob)) elements swapped **before** the main content. In htmx 4, the main content swaps first. OOB and [``](https://four.htmx.org/docs/core-concepts/multi-target-updates#partials-hx-partial) elements swap after (in document order). This matters if an OOB swap creates or modifies DOM that the main swap depends on. If your app relies on that ordering, restructure so each swap is independent. ##### `hx-trigger` `queue` modifier removed The `queue` modifier on [`hx-trigger`](https://four.htmx.org/reference/attributes/hx-trigger) (e.g. `hx-trigger="click queue:all"`) no longer works. Request queuing is now controlled exclusively by [`hx-sync`](https://four.htmx.org/reference/attributes/hx-sync). ```html
...
...
``` ##### 60-second timeout htmx 2 had no timeout (`0`). htmx 4 sets [`defaultTimeout`](https://four.htmx.org/reference/config/htmx-config-defaultTimeout) to `60000`. Revert: `htmx.config.defaultTimeout = 0` ##### Extension loading Include extension scripts directly. No attribute needed: ```html ``` Restrict which extensions can load: ```html ``` Extension authors use `htmx.registerExtension(name, methodMap)` to register. See [Extensions documentation](https://four.htmx.org/docs/features/extensions) for details. #### Renames and Removals ##### Rename `hx-disable` Do this **before** upgrading. The name `hx-disable` has been reassigned: - In htmx 2, `hx-disable` meant "skip htmx processing on this element" - In htmx 4, that role is [`hx-ignore`](https://four.htmx.org/reference/attributes/hx-ignore) - The name `hx-disable` now does what `hx-disabled-elt` used to do (disable form elements during requests) Rename in this order to avoid conflicts: 1. Rename `hx-disable` to [`hx-ignore`](https://four.htmx.org/reference/attributes/hx-ignore) 2. Rename `hx-disabled-elt` to [`hx-disable`](https://four.htmx.org/reference/attributes/hx-disable) ##### Removed attributes | Removed | Use instead | |------------------|---------------------------------------------------------------------------------------------------| | `hx-vars` | [`hx-vals`](https://four.htmx.org/reference/attributes/hx-vals) with `js:` prefix | | `hx-params` | [`htmx:config:request`](https://four.htmx.org/reference/events/htmx-config-request) event | | `hx-prompt` | [`hx-confirm`](https://four.htmx.org/reference/attributes/hx-confirm) with `js:` prefix | | `hx-ext` | [Include extension script directly](https://four.htmx.org/docs/features/extensions) | | `hx-disinherit` | Not needed (inheritance is explicit) | | `hx-inherit` | Not needed (inheritance is explicit) | | `hx-request` | [`hx-config`](https://four.htmx.org/reference/attributes/hx-config) | | `hx-history` | Removed (no [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)) | ##### Renamed events All events follow a new pattern: `htmx:phase:action[:sub-action]` Most error events are consolidated to [`htmx:error`](https://four.htmx.org/reference/events/htmx-error). HTTP error responses have a dedicated [`htmx:response:error`](https://four.htmx.org/reference/events/htmx-response-error) event. | htmx 2.x | htmx 4.x | |-----------------------------|-----------------------------------------------------------------------------------| | `htmx:afterOnLoad` | [`htmx:after:init`](https://four.htmx.org/reference/events/htmx-after-init) | | `htmx:afterProcessNode` | [`htmx:after:init`](https://four.htmx.org/reference/events/htmx-after-init) | | `htmx:afterRequest` | [`htmx:after:request`](https://four.htmx.org/reference/events/htmx-after-request) | | `htmx:afterSettle` | [`htmx:after:swap`](https://four.htmx.org/reference/events/htmx-after-swap) | | `htmx:afterSwap` | [`htmx:after:swap`](https://four.htmx.org/reference/events/htmx-after-swap) | | `htmx:beforeCleanupElement` | [`htmx:before:cleanup`](https://four.htmx.org/reference/events/htmx-before-cleanup) | | `htmx:beforeHistorySave` | [`htmx:before:history:update`](https://four.htmx.org/reference/events/htmx-before-history-update) | | `htmx:beforeOnLoad` | [`htmx:before:init`](https://four.htmx.org/reference/events/htmx-before-init) | | `htmx:beforeProcessNode` | [`htmx:before:process`](https://four.htmx.org/reference/events/htmx-before-process) | | `htmx:beforeRequest` | [`htmx:before:request`](https://four.htmx.org/reference/events/htmx-before-request) | | `htmx:beforeSwap` | [`htmx:before:swap`](https://four.htmx.org/reference/events/htmx-before-swap) | | `htmx:configRequest` | [`htmx:config:request`](https://four.htmx.org/reference/events/htmx-config-request) | | `htmx:historyCacheMiss` | [`htmx:before:history:restore`](https://four.htmx.org/reference/events/htmx-before-restore-history) | | `htmx:historyRestore` | [`htmx:before:history:restore`](https://four.htmx.org/reference/events/htmx-before-restore-history) | | `htmx:load` | [`htmx:after:init`](https://four.htmx.org/reference/events/htmx-after-init) | | `htmx:oobAfterSwap` | [`htmx:after:swap`](https://four.htmx.org/reference/events/htmx-after-swap) | | `htmx:oobBeforeSwap` | [`htmx:before:swap`](https://four.htmx.org/reference/events/htmx-before-swap) | | `htmx:pushedIntoHistory` | [`htmx:after:history:push`](https://four.htmx.org/reference/events/htmx-after-push-into-history) | | `htmx:replacedInHistory` | [`htmx:after:history:replace`](https://four.htmx.org/reference/events/htmx-after-replace-into-history) | | `htmx:responseError` | [`htmx:response:error`](https://four.htmx.org/reference/events/htmx-response-error) | | `htmx:sendError` | [`htmx:error`](https://four.htmx.org/reference/events/htmx-error) | | `htmx:swapError` | [`htmx:error`](https://four.htmx.org/reference/events/htmx-error) | | `htmx:targetError` | [`htmx:error`](https://four.htmx.org/reference/events/htmx-error) | | `htmx:timeout` | [`htmx:error`](https://four.htmx.org/reference/events/htmx-error) | ##### Removed events Validation events are removed. Use native browser form validation: - `htmx:validation:validate` - `htmx:validation:failed` - `htmx:validation:halted` XHR events are removed (htmx uses `fetch()` now): | Removed | Use instead | |----------------------|------------------------------------------------------------------| | `htmx:xhr:loadstart` | No replacement | | `htmx:xhr:loadend` | [`htmx:finally:request`](https://four.htmx.org/reference/events/htmx-finally-request) | | `htmx:xhr:progress` | No replacement | | `htmx:xhr:abort` | [`htmx:error`](https://four.htmx.org/reference/events/htmx-error) | ##### Config changes **Renamed:** | htmx 2.x | htmx 4.x | |--------------------------|----------------------------------------------------------------------------| | `defaultSwapStyle` | [`defaultSwap`](https://four.htmx.org/reference/config/htmx-config-defaultSwap) | | `globalViewTransitions` | [`transitions`](https://four.htmx.org/reference/config/htmx-config-transitions) | | `historyEnabled` | [`history`](https://four.htmx.org/reference/config/htmx-config-history) | | `includeIndicatorStyles` | [`includeIndicatorCSS`](https://four.htmx.org/reference/config/htmx-config-includeIndicatorCSS) | | `timeout` | [`defaultTimeout`](https://four.htmx.org/reference/config/htmx-config-defaultTimeout) | **Changed defaults:** | Config | htmx 2 | htmx 4 | |--------------------------------------------------------------------------|------------------|----------------------| | [`defaultTimeout`](https://four.htmx.org/reference/config/htmx-config-defaultTimeout) | `0` (no timeout) | `60000` (60 seconds) | | [`defaultSettleDelay`](https://four.htmx.org/reference/config/htmx-config-defaultSettleDelay) | `20` | `1` | **Removed:** `addedClass`, `allowEval`, `allowNestedOobSwaps`, `allowScriptTags`, `attributesToSettle`, `defaultSwapDelay`, `disableSelector` (use [`hx-ignore`](https://four.htmx.org/reference/attributes/hx-ignore)), `getCacheBusterParam`, `historyCacheSize`, `ignoreTitle` (still works per-swap via [`hx-swap`](https://four.htmx.org/reference/attributes/hx-swap)`="... ignoreTitle:true"`), `inlineStyleNonce` (removed — indicator CSS now uses [Constructable Stylesheets](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet) and does not require a nonce), `methodsThatUseUrlParams`, `refreshOnHistoryMiss`, `responseHandling` (use [ `hx-status`](https://four.htmx.org/reference/attributes/hx-status) and [`noSwap`](https://four.htmx.org/reference/config/htmx-config-noSwap)), `scrollBehavior`, `scrollIntoViewOnBoost`, `selfRequestsOnly` (use [`htmx.config.mode`](https://four.htmx.org/reference/config/htmx-config-mode)), `settlingClass`, `swappingClass`, `triggerSpecsCache`, `useTemplateFragments`, `withCredentials` (use [ `hx-config`](https://four.htmx.org/reference/attributes/hx-config)), `wsBinaryType`, `wsReconnectDelay` The `htmx-swapping`, `htmx-settling`, and `htmx-added` CSS classes are still applied during swaps. The config keys to customize their names have been removed. ##### Request headers | htmx 2.x | htmx 4.x | Notes | |-------------------|---------------------------------------------------------|------------------------------------------------------------------------| | `HX-Trigger` | [`HX-Source`](https://four.htmx.org/reference/headers/HX-Source) | Format changed to `tagName#id` (e.g. `button#submit`) | | `HX-Target` | [`HX-Target`](https://four.htmx.org/reference/headers/HX-Target) | Format changed to `tagName#id` | | `HX-Trigger-Name` | removed | Use [`HX-Source`](https://four.htmx.org/reference/headers/HX-Source) | | `HX-Prompt` | removed | Use [`hx-confirm`](https://four.htmx.org/reference/attributes/hx-confirm) with `js:` prefix | | *(new)* | [`HX-Request-Type`](https://four.htmx.org/reference/headers/HX-Request-Type) | `"full"` or `"partial"` | | *(new)* | [`Accept`](https://four.htmx.org/reference/headers/Accept) | Now explicitly `text/html` | ##### Response headers Removed: - `HX-Trigger-After-Swap` - `HX-Trigger-After-Settle` Use [`HX-Trigger`](https://four.htmx.org/reference/headers/HX-Trigger) or JavaScript instead. Unchanged: [`HX-Trigger`](https://four.htmx.org/reference/headers/HX-Trigger), [`HX-Location`](https://four.htmx.org/reference/headers/HX-Location), [ `HX-Push-Url`](https://four.htmx.org/reference/headers/HX-Push-Url), [`HX-Redirect`](https://four.htmx.org/reference/headers/HX-Redirect), [ `HX-Refresh`](https://four.htmx.org/reference/headers/HX-Refresh), [`HX-Replace-Url`](https://four.htmx.org/reference/headers/HX-Replace-Url), [ `HX-Retarget`](https://four.htmx.org/reference/headers/HX-Retarget), [`HX-Reswap`](https://four.htmx.org/reference/headers/HX-Reswap), [`HX-Reselect`](https://four.htmx.org/reference/headers/HX-Reselect). ##### JavaScript API changes **Removed methods.** Use native JavaScript: | htmx 2.x | Use instead | |----------------------|------------------------------------------------------------------------| | `htmx.addClass()` | `element.classList.add()` | | `htmx.removeClass()` | `element.classList.remove()` | | `htmx.toggleClass()` | `element.classList.toggle()` | | `htmx.closest()` | `element.closest()` | | `htmx.remove()` | `element.remove()` | | `htmx.off()` | `removeEventListener()` (`htmx.on()` returns the callback) | | `htmx.location()` | `htmx.ajax()` | **Renamed:** `htmx.defineExtension()` is now `htmx.registerExtension()`. **Still available:** `htmx.ajax()`, `htmx.config`, `htmx.find()`, `htmx.findAll()`, `htmx.on()`, `htmx.onLoad()`, `htmx.parseInterval()`, `htmx.process()`, `htmx.swap()`, `htmx.trigger()`. **Removed:** `htmx.logAll()`, `htmx.logNone()`, and the pluggable `htmx.logger`. htmx now logs directly via `console.error` / `console.warn` / `console.log`. Set `htmx.config.logAll = true` to surface event-level output. Observability tools (Sentry, DataDog RUM, LogRocket, etc.) capture `console.*` automatically. Note: `htmx.onLoad()` now listens on [`htmx:after:process`](https://four.htmx.org/reference/events/htmx-after-process), not [ `htmx:after:init`](https://four.htmx.org/reference/events/htmx-after-init). #### What's New ##### Attributes | Attribute | Purpose | |----------------------------------------------------|-----------------------------------------------------------------------| | [`hx-action`](https://four.htmx.org/reference/attributes/hx-action) | Specify URL (use with [`hx-method`](https://four.htmx.org/reference/attributes/hx-method)) | | [`hx-method`](https://four.htmx.org/reference/attributes/hx-method) | Specify HTTP method | | [`hx-config`](https://four.htmx.org/reference/attributes/hx-config) | Per-element request config (JSON or `key:value` syntax) | | [`hx-ignore`](https://four.htmx.org/reference/attributes/hx-ignore) | Disable htmx processing (was `hx-disable`) | | [`hx-validate`](https://four.htmx.org/reference/attributes/hx-validate) | Control form validation behavior | ##### [`hx-swap`](https://four.htmx.org/reference/attributes/hx-swap) scroll modifiers The `show` and `scroll` modifiers no longer support the combined `selector:position` syntax. Use separate keys instead: ```html
``` ##### [`hx-swap`](https://four.htmx.org/reference/attributes/hx-swap) styles ```html
...
...
...
...
``` - `innerMorph` / `outerMorph`: morph swaps using the idiomorph algorithm. Better for preserving state in complex UIs. - `textContent`: set the target's text content (no HTML parsing). - `delete`: remove the target element entirely. New aliases for existing swap styles (both old and new names work): | New | Equivalent to | |-----------|---------------| | `before` | `beforebegin` | | `after` | `afterend` | | `prepend` | `afterbegin` | | `append` | `beforeend` | ##### [Status code swaps](https://four.htmx.org/reference/attributes/hx-status) Set different swap behavior per HTTP status code: ```html
``` Available config keys: `swap:`, `target:`, `select:`, `push:`, `replace:`, `transition:`. Supports exact codes (`404`), single-digit wildcards (`50x`), and range wildcards (`5xx`). Evaluated in order of specificity. ##### `` Target multiple elements from one response. An alternative to [`hx-swap-oob`](https://four.htmx.org/reference/attributes/hx-swap-oob) for when you need explicit control over targeting and swap strategy: ```html
New message
5 ``` Each `` specifies its own [`hx-target`](https://four.htmx.org/reference/attributes/hx-target) and [`hx-swap`](https://four.htmx.org/reference/attributes/hx-swap) strategy. See [Multi-Target Updates](https://four.htmx.org/docs/core-concepts/multi-target-updates) for full documentation. ##### View transitions [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API) support is available but disabled by default. Enable: [`htmx.config.transitions`](https://four.htmx.org/reference/config/htmx-config-transitions) `= true` ##### JSX compatibility Frameworks that don't support `:` in attribute names can use [ `metaCharacter`](https://four.htmx.org/reference/config/htmx-config-metaCharacter) to replace it: ```js htmx.config.metaCharacter = "-"; // hx-ws-connect instead of hx-ws:connect // hx-confirm-inherited instead of hx-confirm:inherited ``` ##### JavaScript methods - `htmx.timeout(time)`: returns a promise that resolves after a delay (number ms, or interval string `'500ms'`/`'1s'`/`'5m'`) `htmx.takeClass` is **removed** from core. Equivalent functionality is exposed by the `hx-live` extension on the `htmx.live` namespace: ```js htmx.live.take(target, className, source) // strip class from `source`, add to `target` htmx.live.forEvent(...args) // race events/timeouts htmx.live.nextFrame() // promise that resolves on next animation frame htmx.live.q(selector) // jQuery-like proxy rooted at documentElement htmx.live.debounce(ms[, fn]) // global debounce htmx.live.refresh() // recompute every live expression ``` Inside `hx-live`/`hx-on` expression scope these are available unprefixed (`take`, `forEvent`, `nextFrame`, `q`, `debounce`, `toggle`) with the current element used as the implicit context — see the [`hx-live` extension docs](https://four.htmx.org/extensions/hx-live). ##### Auto-logged events Internally-dispatched events route to the console as follows: - If `detail.error` is set on the event, output goes to `console.error` (the Error instance is inlined first when applicable, so DevTools renders the stack). This covers request failures, hx-on handler exceptions, and other thrown paths. Apps that listen for `htmx:error` get the same data via the event. - If `detail.warn` is set, output goes to `console.warn`. - Otherwise, the event is logged at `console.log` (silent by default; set `htmx.config.logAll = true` to surface). This restores the htmx 2.x convention: if you want an internal failure path to show up in the console, fire an event with `detail.error` (or `detail.warn`); no per-site `console.error` needed. ##### Request context All events provide a consistent `ctx` object with request/response information. ##### Events | Event | Fires | |------------------------------------------------------------------------------|---------------------------------------------------| | [`htmx:after:cleanup`](https://four.htmx.org/reference/events/htmx-after-cleanup) | After element cleanup | | [`htmx:after:history:update`](https://four.htmx.org/reference/events/htmx-after-history-update) | After history update | | [`htmx:after:process`](https://four.htmx.org/reference/events/htmx-after-process) | After element processing | | [`htmx:before:response`](https://four.htmx.org/reference/events/htmx-before-response) | Before response body is read (cancellable) | | [`htmx:before:settle`](https://four.htmx.org/reference/events/htmx-before-settle) | Before settle phase | | [`htmx:after:settle`](https://four.htmx.org/reference/events/htmx-after-settle) | After settle phase | | [`htmx:before:viewTransition`](https://four.htmx.org/reference/events/htmx-before-viewTransition) | Before a view transition starts (cancellable) | | [`htmx:after:viewTransition`](https://four.htmx.org/reference/events/htmx-after-viewTransition) | After a view transition completes | | [`htmx:finally:request`](https://four.htmx.org/reference/events/htmx-finally-request) | Always fires after a request (success or failure) | ##### Config keys | Config | Default | Purpose | |------------------------------------------------------------------------|-----------------|---------------------------------------------------------------| | [`extensions`](https://four.htmx.org/reference/config/htmx-config-extensions) | `''` | Comma-separated list of allowed extension names | | [`mode`](https://four.htmx.org/reference/config/htmx-config-mode) | `'same-origin'` | Fetch mode (replaces `selfRequestsOnly`) | | [`inlineScriptNonce`](https://four.htmx.org/reference/config/htmx-config-inlineScriptNonce) | `''` | Nonce for inline scripts | | [`metaCharacter`](https://four.htmx.org/reference/config/htmx-config-metaCharacter) | `':'` | Separator character in attribute/event names | | [`morphIgnore`](https://four.htmx.org/reference/config/htmx-config-morphIgnore) | `''` | CSS selector for elements to ignore during morph | | [`morphScanLimit`](https://four.htmx.org/reference/config/htmx-config-morphScanLimit) | | Max elements to scan during morph matching | | [`morphSkip`](https://four.htmx.org/reference/config/htmx-config-morphSkip) | `''` | CSS selector for elements to skip during morph | | [`morphSkipChildren`](https://four.htmx.org/reference/config/htmx-config-morphSkipChildren) | `''` | CSS selector for elements whose children to skip during morph | ##### Core extensions htmx 4 ships with 9 core extensions. The SSE and WebSocket extensions have been significantly rewritten. See their upgrade guides for details. | Extension | Description | |-----------------------------------------------------------|--------------------------------------------------------------------------------------| | [`alpine-compat`](https://four.htmx.org/extensions/hx-alpine-compat) | Alpine.js compatibility: initializes Alpine on fragments before swap | | [`browser-indicator`](https://four.htmx.org/extensions/hx-browser-indicator) | Shows the browser's native loading indicator during requests | | [`head-support`](https://four.htmx.org/extensions/hx-head) | Merges head tag information (styles, etc.) in htmx requests | | [`htmx-2-compat`](https://four.htmx.org/extensions/htmx-2-compat) | Restores implicit inheritance, old event names, and previous error-swapping defaults | | [`optimistic`](https://four.htmx.org/extensions/hx-optimistic) | Shows expected content from a template before the server responds | | [`preload`](https://four.htmx.org/extensions/hx-preload) | Triggers requests early (on mouseover/mousedown) for near-instant page loads ([upgrade guide](https://four.htmx.org/extensions/hx-preload#upgrading-from-htmx-2x)) | | [`sse`](https://four.htmx.org/extensions/hx-sse) | Server-Sent Events streaming support ([upgrade guide](https://four.htmx.org/extensions/hx-sse#upgrading-from-htmx-2x)) | | [`upsert`](https://four.htmx.org/extensions/hx-upsert) | Updates existing elements by ID and inserts new ones, preserving unmatched elements | | [`ws`](https://four.htmx.org/extensions/hx-ws) | Bi-directional WebSocket communication ([upgrade guide](https://four.htmx.org/extensions/hx-ws#upgrading-from-htmx-2x)) | #### Checklist 1. Optionally, add config options or load [`htmx-2-compat`](https://four.htmx.org/extensions/htmx-2-compat) for backward compatibility 2. Run the [upgrade checker](#upgrade-checker) to get a full list of issues 3. Rename `hx-disable` to [`hx-ignore`](https://four.htmx.org/reference/attributes/hx-ignore), then `hx-disabled-elt` to [ `hx-disable`](https://four.htmx.org/reference/attributes/hx-disable) 4. Replace removed attributes with alternatives 5. Find/replace event names in JavaScript and `hx-on` attributes 6. Replace removed API methods with native JS 7. Update extensions 8. Rename changed config keys 9. Test error handling (4xx/5xx now swap by default) 10. Test attribute inheritance 11. Test history navigation #### Migration Notes Individual documentation pages include migration notes where features changed. Look for these:
Changes in htmx 4.0
#### Get Help - [GitHub Discussions](https://github.com/bigskysoftware/htmx/discussions) - [Discord](https://htmx.org/discord) - [Patterns](https://four.htmx.org/patterns) #### Migrating Your Own Extensions If you maintain a custom extension written for htmx 2.x, the extension API has changed substantially. The catalog of bundled extensions ([/extensions](https://four.htmx.org/extensions)) has already been ported. This section is for porting your own. ##### Quick Start htmx 4 replaces the callback-based extension API with event-based hooks. Extensions register handlers for lifecycle events instead of implementing callback methods. The simplest migration: rename `defineExtension` to `registerExtension` and map your callbacks to hooks. ```javascript // htmx 2.x htmx.defineExtension('my-ext', { onEvent: function(name, evt) { if (name === 'htmx:beforeRequest') { /* ... */ } } }); // htmx 4 htmx.registerExtension('my-ext', { htmx_before_request: (elt, detail) => { /* ... */ } }); ``` ##### What Changed ###### No `hx-ext` attribute Extensions load by including the script. No attribute needed: ```html ``` Restrict which extensions can load: ```html ``` ###### Event hooks replace callbacks Instead of a single `onEvent` callback that switches on event names, each event gets its own hook method. Hook names use underscores where events use colons: | htmx 2.x event | htmx 4 hook | |---|---| | `htmx:configRequest` | `htmx_config_request` | | `htmx:beforeRequest` | `htmx_before_request` | | `htmx:afterRequest` | `htmx_after_request` | | `htmx:beforeSwap` | `htmx_before_swap` | | `htmx:afterSwap` | `htmx_after_swap` | All hooks receive `(elt, detail)`. Return `false` to cancel. ###### `handle_swap` is special Unlike other hooks, `handle_swap` is called directly with positional parameters (no `htmx_` prefix, no detail object): ```javascript handle_swap: (swapStyle, target, fragment, swapSpec) => { if (swapStyle === 'my-swap') { target.appendChild(fragment); return true; } return false; } ``` ###### Detail object replaces event properties All hooks receive `detail.ctx` with full request/response context: - `detail.ctx.request.body` (FormData in `htmx_config_request`) - `detail.ctx.request.headers` - `detail.ctx.response.status` - `detail.ctx.text` (response body, modifiable in `htmx_after_request`) - `detail.ctx.target` ###### OOB swap stripping OOB swaps automatically strip the wrapper element for non-outer swap styles. Name custom swap styles starting with "outer" (e.g., `outerMorph`) to preserve the wrapper. ##### Callback Migration Map ###### `init` ```javascript // htmx 2.x init: function(api) { return null; } // htmx 4 init: (internalAPI) => { api = internalAPI; } ``` Store the `internalAPI` reference for use in other hooks. No return value needed. ###### `getSelectors` Removed. Use `htmx_after_init` to check for attributes: ```javascript // htmx 2.x getSelectors: function() { return ['[my-custom-attr]']; }, onEvent: function(name, evt) { if (name === 'htmx:afterProcessNode') { initializeCustomBehavior(evt.target); } } // htmx 4 htmx_after_init: (elt) => { if (api.attributeValue(elt, 'my-custom-attr')) { initializeCustomBehavior(elt); } } ``` ###### `onEvent` Replace with individual hooks: ```javascript // htmx 2.x onEvent: function(name, evt) { if (name === 'htmx:beforeSwap' && evt.detail.xhr.status !== 200) { var target = getRespCodeTarget(evt.detail.requestConfig.elt, evt.detail.xhr.status); if (target) { evt.detail.shouldSwap = true; evt.detail.target = target; } } } // htmx 4 htmx_before_swap: (elt, detail) => { if (detail.ctx.response.status !== 200) { var target = getRespCodeTarget(elt, detail.ctx.response.status); if (target) { detail.ctx.target = target; } } } ``` ###### `transformResponse` Removed. Modify `detail.ctx.text` in `htmx_after_request`: ```javascript // htmx 2.x transformResponse: function(text, xhr, elt) { var tpl = htmx.closest(elt, '[mustache-template]'); if (tpl) { var data = JSON.parse(text); var template = htmx.find('#' + tpl.getAttribute('mustache-template')); return Mustache.render(template.innerHTML, data); } return text; } // htmx 4 htmx_after_request: (elt, detail) => { var tpl = elt.closest('[mustache-template]'); if (tpl) { var data = JSON.parse(detail.ctx.text); var template = document.querySelector('#' + tpl.getAttribute('mustache-template')); detail.ctx.text = Mustache.render(template.innerHTML, data); } } ``` Event flow: response received, `ctx.text` set, `htmx:after:request` fires, `ctx.text` consumed into fragment, `htmx:before:swap`. ###### `encodeParameters` Removed. Modify `detail.ctx.request.body` in `htmx_config_request`: ```javascript // htmx 2.x onEvent: function(name, evt) { if (name === 'htmx:configRequest') { evt.detail.headers['Content-Type'] = 'application/json'; } }, encodeParameters: function(xhr, parameters, elt) { var object = {}; parameters.forEach(function(value, key) { if (Object.hasOwn(object, key)) { if (!Array.isArray(object[key])) object[key] = [object[key]]; object[key].push(value); } else { object[key] = value; } }); return JSON.stringify(object); } // htmx 4 htmx_config_request: (elt, detail) => { detail.ctx.request.headers['Content-Type'] = 'application/json'; var object = {}; detail.ctx.request.body.forEach(function(value, key) { if (Object.hasOwn(object, key)) { if (!Array.isArray(object[key])) object[key] = [object[key]]; object[key].push(value); } else { object[key] = value; } }); detail.ctx.request.body = JSON.stringify(object); } ``` `ctx.request.body` is FormData in `htmx_config_request`. It can be replaced with any value (string, JSON, URLSearchParams). For GET/DELETE, body becomes query parameters. For POST/PUT/PATCH, body becomes URLSearchParams (unless multipart). ###### `isInlineSwap` and `handleSwap` Both replaced by `handle_swap`: ```javascript // htmx 2.x isInlineSwap: function(swapStyle) { return swapStyle === 'morphdom'; }, handleSwap: function(swapStyle, target, fragment) { if (swapStyle === 'morphdom') { morphdom(target, fragment.firstElementChild || fragment.firstChild); return [target]; } } // htmx 4 handle_swap: (swapStyle, target, fragment) => { if (swapStyle === 'morphdom') { morphdom(target, fragment.firstElementChild || fragment.firstChild); return true; } return false; } ``` Return truthy if handled, falsy otherwise. Can return an array of elements for settle tracking. ##### Removed Callbacks | htmx 2.x callback | htmx 4 replacement | |---|---| | `getSelectors()` | `htmx_after_init` hook | | `onEvent(name, evt)` | Individual `htmx_*` hooks | | `transformResponse(text, xhr, elt)` | `htmx_after_request` hook (modify `detail.ctx.text`) | | `encodeParameters(xhr, params, elt)` | `htmx_config_request` hook (modify `detail.ctx.request.body`) | | `isInlineSwap(swapStyle)` | `handle_swap` or name swap style with "outer" prefix | | `handleSwap(style, target, frag, info)` | `handle_swap(style, target, frag, spec)` | ##### Checklist 1. Rename `defineExtension` to `registerExtension` 2. Replace `onEvent` with individual `htmx_*` hooks 3. Replace `transformResponse` with `htmx_after_request` 4. Replace `encodeParameters` with `htmx_config_request` 5. Merge `isInlineSwap` and `handleSwap` into `handle_swap` 6. Replace `getSelectors` with `htmx_after_init` 7. Remove `hx-ext` attributes from HTML 8. Update event names (colons to underscores in hook names) 9. Test custom swap styles with OOB swaps ## Core Concepts ### [Mental Model](https://four.htmx.org/docs/core-concepts/mental-model) htmx extends HTML's built-in concept of [hypermedia controls](https://dl.acm.org/doi/fullHtml/10.1145/3648188.3675127). To understand htmx, you should first understand what these are. #### HTML's Native Controls HTML has two major elements that issue HTTP requests in response to user actions: `` (anchors, aka "links") and `
`. ##### The Anchor Tag ```html Blog ``` When a user click this link a browser will issue an HTTP `GET` request to `/blog`. It will then load the HTML response into the browser's window. ##### The Form Tag ```html
``` When a user submits this form (by, say, clicking on the "Submit" button) a browser will issue an HTTP `POST` request to `/register`. Again, it will load the HTML response to this request into the browser window. These two hypermedia controls demonstrate the core idea behind them: in response to a user action a request is made and new content is loaded into the client. #### Transclusion Both of these HTML elements support a (relatively little-known and unused) [`target`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement/target) attribute. By using this attribute you can place the response in, for example, an iframe rather than replacing the entire content in the window: ```html
``` This is [transclusion](https://en.wikipedia.org/wiki/Transclusion), where one HTML document is included inside of another. #### How htmx Extends This Idea htmx generalizes these concepts. Any element can issue any type of HTTP request to any URL. Any event can trigger the request. And the response HTML can be placed anywhere in the DOM. ```html ``` These htmx attributes (which start with `hx-`) tell a browser: > When a user clicks this button, issue a POST request to `/clicked`. Use the response to replace the element with id `output`. Like anchor and form tags, htmx expects _HTML responses_ from the server. This is in contrast with many front-end libraries and frameworks today which instead expect JSON and use client-side templating to transform that JSON into HTML on the client. #### htmx's Philosophy Because htmx works in terms of HTML it follows the [original web programming model](https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm). It uses [Hypertext As The Engine Of Application State](https://en.wikipedia.org/wiki/HATEOAS) (HATEOAS). The server controls what the user sees by sending HTML. Users select actions from that HTML and the server responds with more HTML (i.e. hypertext). Thus, the hypermedia itself drives the application. ### [Hypermedia Controls](https://four.htmx.org/docs/core-concepts/hypermedia-controls) htmx extends HTML with attributes that control how requests are made and how responses update the page. #### Making Requests Add [`hx-get`](https://four.htmx.org/reference/attributes/hx-get) to an element. It makes an AJAX request when clicked. **Your HTML:** ```html ``` **What the server returns:** ```html
You have 3 new messages
``` **What the user sees:** ```html ``` The response replaced the button's content. No JavaScript required. ##### How It Works | Step | What Happens | |-----------------------|----------------------------------| | 1. User clicks button | htmx intercepts the click | | 2. htmx makes request | Sends GET request to `/messages` | | 3. Server responds | Returns HTML (not JSON) | | 4. htmx updates page | Swaps HTML into the button | ##### HTTP Methods Use different attributes for different operations: ```html ``` Each attribute combines the URL and HTTP method. ##### Common Patterns **Load data on click:** ```html ``` **Submit a form:** ```html
``` Form submits via AJAX instead of full page reload. **Delete an item:** ```html ``` ##### What Gets Sent htmx sends standard HTTP requests: **Request to server:** ``` GET /messages HTTP/1.1 HX-Request: true HX-Target: button ``` htmx adds custom headers so your server knows it's an htmx request. **Server response:** ```html HTTP/1.1 200 OK Content-Type: text/html
You have 3 new messages
``` Just HTML. No JSON parsing needed. #### Triggers By default, requests are triggered by the "natural" event of an element: * `input`, `textarea` & `select` are triggered on the `change` event * `form` is triggered on the `submit` event * everything else is triggered by the `click` event If you want different behavior you can use the [`hx-trigger`](https://four.htmx.org/reference/attributes/hx-trigger) attribute to specify which event will cause the request. Here is a `div` that posts to `/mouse_entered` when a mouse enters it: ```html
Mouse Trap
``` ##### Trigger Modifiers A trigger can also have additional modifiers that change its behavior. For example, if you want a request to only happen once, you can use the `once` modifier for the trigger: ```html
Mouse Trap
``` Other modifiers you can use for triggers are (parsed as [HCON](https://four.htmx.org/docs/core-concepts/hcon#hx-trigger-modifiers)): * `changed` - only issue a request if the value of the element has changed * `delay: