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.

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:before:swap', 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