How to Define Custom htmx Extensions: A Complete Guide to the Extension API
Register a custom htmx extension by calling htmx.defineExtension(name, definition) with an object implementing any of the seven lifecycle hooks—init, getSelectors, onEvent, transformResponse, isInlineSwap, handleSwap, or encodeParameters—then activate it on any element with hx-ext="your-name".
The htmx library provides a robust extension system that allows developers to intercept requests, transform responses, and implement custom swap behaviors without modifying the core codebase. According to the bigskysoftware/htmx source code, extensions are registered in a global registry and integrated during the DOM traversal phase, making them both composable and scope-aware. This guide explains how to define custom htmx extensions using the official API, referencing the actual implementation in src/htmx.js.
The Extension Registry and defineExtension()
At the heart of the extension system is htmx.defineExtension(), a method attached to the global htmx object. Internally, htmx maintains an extensions map in src/htmx.js (lines 4978–5009) where each registered extension is stored by name. When you define an extension, your definition object is merged with a base template that specifies the available hooks.
htmx.defineExtension('my-extension', {
init: function(api) { /* initialization logic */ },
onEvent: function(name, evt) { return true; }
});
The init hook receives the internal htmx API object, allowing extensions to tap into internal utilities immediately upon registration.
Core Extension Hooks and Lifecycle
Each extension can implement any subset of the seven default extension points defined by extensionBase() in the source. These hooks allow precise control over the request lifecycle.
init(api)– Called once when the extension is defined; receives the internal htmx API for advanced integration.getSelectors()– Returns CSS selectors that automatically activate the extension when matching elements are processed.onEvent(name, evt)– Intercepts htmx events such ashtmx:beforeRequestorhtmx:afterSwap; returntrueto allow default handling.transformResponse(text, xhr, elt)– Modifies the raw server response text before it is swapped into the DOM.isInlineSwap(swapStyle)– Declares whether a custom swap style should be treated as an inline swap.handleSwap(swapStyle, target, fragment, settleInfo)– Performs custom DOM insertion; returntrueto indicate the swap was handled.encodeParameters(xhr, parameters, elt)– Serializes request parameters, enabling custom formats like JSON bodies.
Defining a Simple Event Logger Extension
The most basic extension hooks into the event system to observe htmx behavior. This example logs every htmx event to the console.
<script>
htmx.defineExtension('log-events', {
onEvent: function(name, evt) {
console.log('htmx event:', name, evt);
return true; // Allow other handlers to run
}
});
</script>
<button hx-get="/info" hx-ext="log-events">Fetch Info</button>
When the button triggers a request, the onEvent hook fires for each lifecycle event, providing visibility into the timing of htmx:beforeRequest, htmx:beforeSwap, and htmx:afterSwap.
Transforming Server Responses
Extensions frequently modify server responses before they reach the DOM. The transformResponse hook receives the raw text, the XMLHttpRequest object, and the triggering element.
// json-pretty.js
htmx.defineExtension('json-pretty', {
transformResponse: function(text, xhr, elt) {
try {
const obj = JSON.parse(text);
return '<pre>' + JSON.stringify(obj, null, 2) + '</pre>';
} catch (e) {
return text; // Fail-safe: return original if not valid JSON
}
}
});
<script src="json-pretty.js"></script>
<div hx-get="/api/data.json" hx-ext="json-pretty"></div>
This extension automatically converts JSON responses into formatted, syntax-highlighted blocks without requiring changes to the server endpoint.
Implementing Custom Swap Animations
For behavior that overrides htmx’s default swapping logic, implement handleSwap. This method receives the swap style, target element, document fragment, and settlement information.
htmx.defineExtension('fade-swap', {
handleSwap: function(swapStyle, target, fragment, settleInfo) {
if (swapStyle === 'fade') {
target.style.opacity = 0;
target.innerHTML = fragment;
requestAnimationFrame(() => {
target.style.transition = 'opacity 0.5s';
target.style.opacity = 1;
});
return true; // Signal that we handled this swap
}
return false; // Defer to htmx for other swap styles
}
});
<div hx-get="/new-content" hx-swap="fade" hx-ext="fade-swap">
Content will fade in
</div>
When hx-swap="fade" is detected, the extension manually manages the DOM insertion and CSS transition. Returning false ensures that standard swap styles like innerHTML or outerHTML still function normally.
Scoping Extensions with hx-ext and DOM Traversal
htmx activates extensions through the hx-ext attribute. When processing an element, the internal getExtensions function (implemented in src/htmx.js, lines 5020–5055) walks up the DOM tree, collecting all extensions specified in comma-separated lists.
<div hx-ext="json-pretty, log-events">
<button hx-get="/data">Both extensions are active here</button>
</div>
Extensions are composable; multiple extensions can operate on the same element, with their hooks executing in the order they were defined. The getSelectors hook allows extensions to auto-activate without explicit hx-ext attributes by returning CSS selectors that trigger automatic registration when matching elements appear in the DOM.
Reference Implementations and Source Files
Study the official extensions in dist/ext/ for production-quality patterns. The WebSocket (ws.js) and Server-Sent Events (sse.js) extensions demonstrate advanced usage of encodeParameters and onEvent to manage persistent connections.
Key files in the bigskysoftware/htmx repository:
src/htmx.js– ContainsdefineExtension, theextensionsregistry, andgetExtensionslogic.www/content/extensions/building.md– Official documentation covering extension theory and best practices.www/content/api.md#defineExtension– API reference forhtmx.defineExtension().dist/ext/– Working examples of official extensions.
Summary
- Registration – Use
htmx.defineExtension(name, definition)to add your extension to the internal registry insrc/htmx.js. - Hooks – Implement any combination of
init,getSelectors,onEvent,transformResponse,isInlineSwap,handleSwap, orencodeParametersto modify behavior. - Activation – Apply extensions via the
hx-extattribute, which supports comma-separated lists for composition. - Scope – Extensions can be scoped to specific elements or automatically activated via
getSelectors. - Fallback – Always return
falsefromhandleSwaporonEventwhen you want htmx to continue with default processing.
Frequently Asked Questions
What is the exact function signature for registering an extension?
The global htmx object exposes htmx.defineExtension(name, definition), where name is a unique string identifier and definition is an object containing any of the seven optional hooks. The method stores the definition in the internal extensions map located at lines 4978–5009 of src/htmx.js.
How can I limit an extension to specific elements only?
Apply the hx-ext attribute directly to the HTML elements or containers where you want the extension active. The internal getExtensions function traverses up the DOM tree from the triggering element and only loads extensions found in hx-ext attributes encountered during that walk.
Can an extension modify the request body before it is sent?
Yes. Implement the encodeParameters(xhr, parameters, elt) hook in your extension definition. This hook receives the XMLHttpRequest object, the parameter data, and the source element, allowing you to serialize parameters as JSON or apply custom encoding logic before the network request dispatches.
Where should I look for production-quality extension examples?
Reference the built-in extensions in the dist/ext/ directory of the repository. Files like ws.js (WebSocket support) and sse.js (Server-Sent Events) demonstrate complex implementations using onEvent for lifecycle management and encodeParameters for custom request formatting.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →