htmx 4 introduces an extension system based on event hooks rather than the old callback-based API.

Defining an Extension

Extensions are defined using htmx.registerExtension():

htmx.registerExtension("my-ext", { init: (internalAPI) => { // Called once when extension is registered // Store internalAPI reference if needed }, htmx_before_request: (elt, detail) => { // Called before each request // Return false to cancel }, htmx_after_request: (elt, detail) => { // Called after each request }, });

Event Hooks

Extensions hook into htmx lifecycle events. Event names use underscores instead of colons:

Core Lifecycle Events

Hook NameTriggered EventParametersDescription
htmx_before_inithtmx:before:init(elt, detail)Before element initialization
htmx_after_inithtmx:after:init(elt, detail)After element initialization
htmx_before_processhtmx:before:process(elt, detail)Before processing element
htmx_after_processhtmx:after:process(elt, detail)After processing element
htmx_before_cleanuphtmx:before:cleanup(elt, detail)Before cleaning up element
htmx_after_cleanuphtmx:after:cleanup(elt, detail)After cleaning up element

Request Lifecycle Events

Hook NameTriggered EventParametersDescription
htmx_config_requesthtmx:config:request(elt, detail)Configure request before sending
htmx_before_requesthtmx:before:request(elt, detail)Before request is sent
htmx_before_responsehtmx:before:response(elt, detail)After fetch, before body consumed
htmx_after_requesthtmx:after:request(elt, detail)After request completes
htmx_finally_requesthtmx:finally:request(elt, detail)When request completes, fails, or is cancelled
htmx_errorhtmx:error(elt, detail)On request error

Swap Events

Hook NameTriggered EventParametersDescription
htmx_before_swaphtmx:before:swap(elt, detail)Before content swap
htmx_after_swaphtmx:after:swap(elt, detail)After content swap
htmx_finally_swaphtmx:finally:swap(elt, detail)After swap (success or error)
htmx_before_settlehtmx:before:settle(elt, detail)Before settle phase
htmx_after_settlehtmx:after:settle(elt, detail)After settle phase
handle_swap(direct call)(swapStyle, target, fragment, swapSpec)Custom swap handler

History Events

Hook NameTriggered EventParametersDescription
htmx_before_history_updatehtmx:before:history:update(elt, detail)Before updating history
htmx_after_history_updatehtmx:after:history:update(elt, detail)After updating history
htmx_after_history_pushhtmx:after:history:push(elt, detail)After pushing to history
htmx_after_history_replacehtmx:after:history:replace(elt, detail)After replacing history
htmx_before_history_restorehtmx:before:history:restore(elt, detail)Before restoring from history

Cancelling Events

Return false or set detail.cancelled = true to cancel an event:

htmx.registerExtension("validator", { htmx_before_request: (elt, detail) => { if (!isValid(detail.ctx)) { return false; // Cancel request } }, });

Internal API

The init hook receives an internal API object with helper methods:

let api; htmx.registerExtension("my-ext", { init: (internalAPI) => { api = internalAPI; }, htmx_after_init: (elt) => { let value = api.attributeValue(elt, "hx-my-attr"); let specs = api.parseTriggerSpecs("click, keyup delay:500ms"); let { method, action } = api.determineMethodAndAction(elt, evt); }, });

Available internal API:

  • HCON - Parse and merge HCON values
  • attributeValue(elt, name, defaultVal, returnElt) - Get an attribute value with inheritance support
  • parseTriggerSpecs(spec) - Parse a trigger specification
  • determineMethodAndAction(elt, evt) - Get {method, action} for an element
  • createRequestContext(elt, evt) - Create a request context
  • collectFormData(elt, form, submitter, validate, isGet) - Collect form data
  • getAttributeObject(elt, name, callback, scope) - Read an object-valued attribute
  • insertContent(task, cssTransition) - Insert a swap task’s content
  • morph(oldNode, fragment, innerHTML) - Morph existing content
  • isSoftMatch(oldNode, newNode) - Test whether two nodes can be morphed
  • initSecurity(ttPolicy, syncFn, asyncFn) - Configure Trusted Types and script constructors
  • onTrigger(elt, spec, handler) - Attach a parsed trigger handler
  • htmxProp(elt) - Get an element’s internal htmx state
  • triggerHtmxEvent(elt, name, detail, bubbles) - Dispatch an htmx event
  • executeJavaScript(thisArg, values, code, expression, isAsync) - Execute JavaScript through htmx security policy

Request Context

The detail.ctx object contains request information:

{ sourceElement, // Element triggering request sourceEvent, // Event that triggered request status, // Request status target, // Target element for swap swap, // Swap strategy request: { action, // Request URL method, // HTTP method headers, // Request headers body, // FormData during htmx_config_request; final BodyInit later validate, // Whether to validate abort, // Function to abort request signal // AbortSignal }, response: { // Available after request raw, // Raw Response object status, // HTTP status code headers // Response headers: https://developer.mozilla.org/en-US/docs/Web/API/Headers }, text, // Response text (after request) hx // HX-* response headers (parsed) }

Custom Swap Strategies

Extensions can implement custom swap strategies:

htmx.registerExtension("my-swap", { handle_swap: (swapStyle, target, fragment, swapSpec) => { if (swapStyle === "my-custom-swap") { target.appendChild(fragment); return true; // Handled } return false; // Not handled }, });

For migrating extensions written for htmx 2.x, see Migrating Extensions to htmx 4.