## 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
`
```
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: