htmx Extension Authoring Guide
The htmx 4 extension system, its event hooks, and the internal API.
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 Name | Triggered Event | Parameters | Description |
|---|---|---|---|
htmx_before_init |
htmx:before:init |
(elt, detail) |
Before element initialization |
htmx_after_init |
htmx:after:init |
(elt, detail) |
After element initialization |
htmx_before_process |
htmx:before:process |
(elt, detail) |
Before processing element |
htmx_after_process |
htmx:after:process |
(elt, detail) |
After processing element |
htmx_before_cleanup |
htmx:before:cleanup |
(elt, detail) |
Before cleaning up element |
htmx_after_cleanup |
htmx:after:cleanup |
(elt, detail) |
After cleaning up element |
Request Lifecycle Events
| Hook Name | Triggered Event | Parameters | Description |
|---|---|---|---|
htmx_config_request |
htmx:config:request |
(elt, detail) |
Configure request before sending |
htmx_before_request |
htmx:before:request |
(elt, detail) |
Before request is sent |
htmx_before_response |
htmx:before:response |
(elt, detail) |
After fetch, before body consumed |
htmx_after_request |
htmx:after:request |
(elt, detail) |
After request completes |
htmx_finally_request |
htmx:finally:request |
(elt, detail) |
When request completes, fails, or is cancelled |
htmx_error |
htmx:error |
(elt, detail) |
On request error |
Swap Events
| Hook Name | Triggered Event | Parameters | Description |
|---|---|---|---|
htmx_before_swap |
htmx:before:swap |
(elt, detail) |
Before content swap |
htmx_after_swap |
htmx:after:swap |
(elt, detail) |
After content swap |
htmx_finally_swap |
htmx:finally:swap |
(elt, detail) |
After swap (success or error) |
htmx_before_settle |
htmx:before:settle |
(elt, detail) |
Before settle phase |
htmx_after_settle |
htmx:after:settle |
(elt, detail) |
After settle phase |
handle_swap |
(direct call) | (swapStyle, target, fragment, swapSpec) |
Custom swap handler |
History Events
| Hook Name | Triggered Event | Parameters | Description |
|---|---|---|---|
htmx_before_history_update |
htmx:before:history:update |
(elt, detail) |
Before updating history |
htmx_after_history_update |
htmx:after:history:update |
(elt, detail) |
After updating history |
htmx_after_history_push |
htmx:after:history:push |
(elt, detail) |
After pushing to history |
htmx_after_history_replace |
htmx:after:history:replace |
(elt, detail) |
After replacing history |
htmx_before_history_restore |
htmx: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.
Licensed under the Zero-Clause BSD License.
https://four.htmx.org/docs/extension-authoring-guide/