htmx 4.0 is under construction — migration guide

Extensions

Load, configure, and build htmx extensions.

htmx supports extensions to augment its core hypermedia infrastructure. The extension mechanism takes pressure off the core library to add new features, allowing it to focus on its main purpose of generalizing hypermedia controls.

For the catalog of core extensions shipped with htmx, see /extensions.

Using Extensions

In htmx 4, extensions hook into standard events rather than callback extension points. They are lightweight with no performance penalty.

Extensions apply page-wide without requiring hx-ext on parent elements. They activate via custom attributes where needed.

Loading an Extension

Include the extension script after htmx. Core extensions ship with htmx in the /ext/ directory:

<script src="https://cdn.jsdelivr.net/npm/htmx.org@next/dist/htmx.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/htmx.org@next/dist/ext/hx-sse.js"></script>

Or with a bundler:

import 'htmx.org'; import 'htmx.org/dist/ext/hx-sse';

Restricting Extensions

To restrict which extensions can register, use an allow list:

<meta name="htmx-config" content='{"extensions": "my-ext,another-ext"}'>

When this config is set, only the listed extensions will be loaded. Without it, all registered extensions are active.

Building Extensions

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)Always called after request
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_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 methods:

  • attributeValue(elt, name, defaultVal, returnElt) - Get htmx attribute value with inheritance
  • parseTriggerSpecs(spec) - Parse trigger specification string
  • determineMethodAndAction(elt, evt) - Get HTTP method and URL
  • createRequestContext(elt, evt) - Create request context object
  • collectFormData(elt, form, submitter) - Collect form data
  • handleHxVals(elt, body) - Process hx-vals attribute

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, // Request body (FormData) 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 }, 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 Migration → Migrating Your Own Extensions.