--- name: htmx-extension-authoring description: Use when creating, modifying, or debugging htmx 4 extensions. Covers the event-based extension API, internal API, lifecycle hooks, and distribution patterns. argument-hint: "[description of extension behavior]" --- # Building htmx 4 Extensions htmx 4 extensions hook into lifecycle events via `htmx.registerExtension()`. Extensions are global -- they apply page-wide, activated by custom attributes where needed. ## Extension Boilerplate ```javascript (() => { let api; htmx.registerExtension('my-ext', { init: (internalAPI) => { api = internalAPI; }, htmx_after_init: (elt, detail) => { // Check for your custom attribute let value = api.attributeValue(elt, "hx-my-attr"); if (!value) return; // Initialize behavior for this element }, htmx_before_request: (elt, detail) => { // Modify request before sending // detail.ctx has request info // Return false to cancel }, htmx_after_request: (elt, detail) => { // After request completes // detail.ctx.text has response text // detail.ctx.response has status, headers }, htmx_before_swap: (elt, detail) => { // Before content swap }, htmx_after_swap: (elt, detail) => { // After content swap }, htmx_before_cleanup: (elt, detail) => { // Clean up event listeners, timers, etc. }, }); })(); ``` ## Loading Extensions Include the script after htmx.js. Optionally restrict which extensions can register: ```html ``` When the `extensions` config is set, only listed extensions load. Without it, all registered extensions are active. The whitelist matches the name passed to `registerExtension()`, not the file name. The shipped extensions are not consistent about this: `hx-sse.js` registers as `sse`, `hx-preload.js` as `preload`, `htmx-2-compat.js` as `compat`, but `hx-live.js` registers as `hx-live`. Pick one and document it for your own extension. ## Event Hooks Reference A hook name is the event name with every colon replaced by an underscore. `htmx:before:morph:node` becomes `htmx_before_morph_node`. Any event htmx dispatches can be hooked this way, including events that other extensions dispatch. All hooks receive `(elt, detail)` unless noted. ### Core Lifecycle | Hook | Event | Description | |------|-------|-------------| | `htmx_before_init` | `htmx:before:init` | Before element initialization | | `htmx_after_init` | `htmx:after:init` | After element initialization | | `htmx_before_process` | `htmx:before:process` | Before processing element | | `htmx_after_process` | `htmx:after:process` | After processing element | | `htmx_before_cleanup` | `htmx:before:cleanup` | Before cleaning up element | | `htmx_after_cleanup` | `htmx:after:cleanup` | After cleaning up element | | `htmx_before_on_init` | `htmx:before:on:init` | Before an `hx-on` handler is installed | ### Request Lifecycle | Hook | Event | Description | |------|-------|-------------| | `htmx_config_request` | `htmx:config:request` | Configure request (modify headers, body, URL) | | `htmx_before_request` | `htmx:before:request` | Before request is sent | | `htmx_before_response` | `htmx:before:response` | After fetch response, before body consumed | | `htmx_after_request` | `htmx:after:request` | After request completes | | `htmx_finally_request` | `htmx:finally:request` | When request completes, fails, or is cancelled | | `htmx_confirm` | `htmx:confirm` | After trigger, before request. Detail carries `issueRequest` and `dropRequest` | | `htmx_error` | `htmx:error` | On any error | | `htmx_response_error` | `htmx:response:error` | The server returned an HTTP error status | ### Swap | Hook | Event | Description | |------|-------|-------------| | `htmx_before_swap` | `htmx:before:swap` | Before content swap | | `htmx_after_swap` | `htmx:after:swap` | After content swap | | `htmx_finally_swap` | `htmx:finally:swap` | After swap (success or error) | | `htmx_before_settle` | `htmx:before:settle` | Before settle phase | | `htmx_after_settle` | `htmx:after:settle` | After settle phase | | `htmx_before_morph_node` | `htmx:before:morph:node` | Before a node is morphed. Cancel to keep the existing node | | `htmx_before_morph_attr` | `htmx:before:morph:attr` | Before an attribute is morphed. Cancel to keep the existing value | | `handle_swap` | _(direct call)_ | Custom swap handler. Signature: `(swapStyle, target, fragment, swapSpec)`. Return truthy if handled. | ### History | Hook | Event | |------|-------| | `htmx_before_history_update` | `htmx:before:history:update` | | `htmx_after_history_update` | `htmx:after:history:update` | | `htmx_after_history_push` | `htmx:after:history:push` | | `htmx_after_history_replace` | `htmx:after:history:replace` | | `htmx_before_history_restore` | `htmx:before:history:restore` | ### View Transitions | Hook | Event | |------|-------| | `htmx_before_viewTransition` | `htmx:before:viewTransition` | | `htmx_after_viewTransition` | `htmx:after:viewTransition` | ### Other | Hook | Event | |------|-------| | `htmx_after_implicitInheritance` | `htmx:after:implicitInheritance` | | `htmx_process_` | _(extensions only)_ | `htmx_process_` handles a `