htmx triggers events at every step of the request and swap lifecycle. These events are the main extension point for scripting htmx. You can use them to log what htmx does, to cancel an action, or to change it before it happens.

Listening For Events

There are three ways to listen for an htmx event.

Use htmx.on(), which listens on document by default:

htmx.on('htmx:after:swap', function (evt) { console.log('swapped', evt.detail.ctx.target); });

Use the standard addEventListener:

document.body.addEventListener('htmx:after:init', function (evt) { setUpElement(evt.detail.elt); });

Or use an hx-on:* attribute on the element itself:

<button hx-post="/example" hx-on:htmx:config:request="ctx.request.body.set('source', 'button')"> Post Me! </button>

htmx events bubble, so a listener on document or document.body sees events from every element. Use an hx-on:* attribute when you only want to handle events from one element.

The Event Detail

Most htmx events carry a detail object. For request and swap events, detail.ctx is the request context. It holds the source element, the target, the swap style, the request, and, after the response arrives, the response.

htmx.on('htmx:before:request', function (evt) { let ctx = evt.detail.ctx; console.log(ctx.request.method, ctx.request.action, ctx.target); });

Inside an hx-on:* attribute every property of detail is already in scope, so you write ctx rather than event.detail.ctx.

Element lifecycle events use detail.elt instead, which is the element htmx processed.

The ctx Object

ctx is the request context object. It is created when a request is triggered and lives until the swap lifecycle ends. Properties marked writable can be changed in an event handler to alter htmx’s behavior.

PropertyTypeWritableDescription
sourceElementElementThe element that triggered the request
sourceEventEventThe DOM event that triggered the request
targetElementyesThe element that will receive the swapped content. Mutate in htmx:after:request. Note: HX-Retarget is applied after this event and will overwrite your value
swapstringyesThe swap style (e.g. "innerHTML"). Mutate in htmx:after:request. Note: HX-Reswap is applied after this event and will overwrite your value
selectstring|nullyesCSS selector from hx-select. Mutate in htmx:after:request. Note: HX-Reselect is applied after this event and will overwrite your value
selectOOBstring|nullyesSelector string from hx-select-oob. Mutate in htmx:after:request
pushstring|nullyesURL to push into history. Mutate in htmx:after:request. Note: HX-Push-Url is applied after this event and will overwrite your value
replacestring|nullyesURL to replace in history. Mutate in htmx:after:request. Note: HX-Replace-Url is applied after this event and will overwrite your value
confirmstring|nullConfirmation message from hx-confirm. Read before any events fire — not writable from an event
transitionbooleanyesWhether to use the View Transitions API. Writable up to and including htmx:before:swap
requestobjectSub-object with request details — see below
responseobjectAdded after fetch(). Has raw (the Response), status (number), headers (Headers)
textstringyesAdded after the response body is read. The raw HTML string. Mutate in htmx:after:request — by htmx:before:swap the fragment is already parsed
titlestringyesPage title extracted from the response fragment. Set during swap() — writable up to htmx:after:swap
hxobjectParsed HX-* response headers. Keys are lowercased with hyphens removed: hx.trigger, hx.retarget, hx.reswap, etc.

ctx.request

ctx.request maps directly onto the Fetch API RequestInit. Changes made in htmx:config:request are applied before the request is sent.

PropertyTypeDescription
actionstringThe request URL
methodstringHTTP method ("GET", "POST", etc.)
headersobjectRequest headers. Add or change headers here
bodyFormData|URLSearchParams|nullRequest body. A FormData at htmx:config:request (the right place to add values). Encoded to URLSearchParams or null for GET/DELETE before fetch()
validatebooleanWhether HTML5 form validation runs before the request
credentialsstringFetch credentials mode. Defaults to "same-origin"
signalAbortSignalAbort signal. Fires when htmx:abort is triggered on the element
abortfunctionCall to abort the in-flight request
modestringFetch mode. Always reset to htmx.config.mode — cannot be overridden per-element
formElement|nullThe associated form element, if any
submitterElement|nullThe submit button that triggered the request, if any
anchorstringURL fragment (the part after #), if present in the action URL
timeoutnumber|stringRequest timeout. Set via hx-config="timeout:5s"

Cancelling An Event

htmx events are cancelable. Call preventDefault() to stop htmx from continuing:

htmx.on('htmx:before:request', function (evt) { if (!isLoggedIn()) { evt.preventDefault(); } });

Cancelling has a different effect for each event:

EventEffect of cancelling
htmx:confirmsuppresses the built-in confirmation. Call detail.issueRequest() or detail.dropRequest() yourself
htmx:before:requestthe request is never sent
htmx:before:responsethe response body is never read and no swap happens
htmx:after:requestthe response is read but no swap happens
htmx:before:swapno swap task runs
htmx:before:initthe element is not initialized
htmx:before:processthe node and its descendants are not processed

The Event Lifecycle

Request Events

These events fire in order for every request.

EventWhen it fires
htmx:confirmbefore htmx handles hx-confirm
htmx:config:requestbefore the request data is encoded. Change ctx.request here
htmx:before:requestimmediately before fetch()
htmx:before:responseafter fetch(), before the body is read
htmx:after:requestafter the response body is read
htmx:response:errorthe response status is 400 or higher
htmx:errora request or swap threw an exception
htmx:finally:requestthe lifecycle ended, including on failure

Swap Events

EventWhen it fires
htmx:before:swapbefore the DOM is updated. detail.tasks holds the planned swaps
htmx:before:settleafter insertion, before the settle tasks
htmx:after:settleafter the settle tasks
htmx:after:swapafter the DOM is updated
htmx:finally:swapat the end of the swap lifecycle
htmx:before:viewTransitionbefore a view transition starts
htmx:after:viewTransitionafter a view transition completes

Element Lifecycle Events

EventWhen it fires
htmx:before:processbefore htmx processes a DOM node
htmx:after:processafter htmx processes a DOM node
htmx:before:initbefore an element is initialized
htmx:after:initafter an element is initialized
htmx:before:cleanupbefore htmx removes its data from an element
htmx:after:cleanupafter htmx removes its data from an element

History Events

EventWhen it fires
htmx:before:history:updatebefore the browser history is updated
htmx:after:history:updateafter the browser history is updated
htmx:after:history:pushafter a URL is pushed
htmx:after:history:replaceafter a URL is replaced
htmx:before:history:restorebefore a page is restored from history

See the events reference for the full list, including the trigger events load, intersect and every.

Common Recipes

Initialize A Third Party Library

New content that htmx swaps in is not known to your other libraries. Listen for htmx:after:init to set it up:

htmx.on('htmx:after:init', function (evt) { setUpElement(evt.detail.elt); });

You can also use htmx.onLoad(), which is a shorthand for this.

Add A Value To Every Request

htmx:config:request fires before the request body is encoded, so ctx.request.body is still a FormData object:

htmx.on('htmx:config:request', function (evt) { evt.detail.ctx.request.body.set('csrf-token', getToken()); });

To add a header instead, write to ctx.request.headers:

htmx.on('htmx:config:request', function (evt) { evt.detail.ctx.request.headers['X-CSRF-Token'] = getToken(); });

Change The Target Or Swap Style

Both are on the context, so you can change them up to the point of the swap:

htmx.on('htmx:after:request', function (evt) { if (evt.detail.ctx.response.status === 404) { evt.detail.ctx.target = document.querySelector('#errors'); } });

For the declarative version of this, see hx-status.

Logging Events

Set htmx.config.logAll to true to log every htmx event to the console:

htmx.config.logAll = true;

You can also set it from HTML:

<meta name="htmx-config" content="logAll:true">

htmx always logs events whose detail carries an error at error level, and events whose detail carries a warn at warning level. logAll adds everything else.

See Also