HTMX Extensions
HTMX's core library is intentionally minimal. It handles the essential request-response cycle but delegates specialized functionality to extensions. Extensions are separate JavaScript files that add new attributes, behaviors, and capabilities to HTMX without bloating the core. This topic covers the most useful official extensions and shows you how to load and use them.
How Extensions Work
Extensions register themselves with HTMX when their script loads. Once registered, they listen for HTMX events and add new attribute-driven behaviors. You activate an extension on an element using hx-ext="extension-name".
Loading an extension: <script src="https://unpkg.com/htmx.org@2.0.0"></script> <script src="https://unpkg.com/htmx-ext-NAME/ext.js"></script> Using an extension: <div hx-ext="NAME">...</div> Extensions inherit down the DOM — putting hx-ext on body activates the extension for the whole page.
Official Extensions Overview
| Extension | Package | What It Adds |
|---|---|---|
| sse | htmx-ext-sse | Server-Sent Events support |
| ws | htmx-ext-ws | WebSocket support |
| json-enc | htmx-ext-json-enc | Sends requests as JSON instead of form-encoded |
| preload | htmx-ext-preload | Preloads linked pages on hover for near-instant navigation |
| class-tools | htmx-ext-class-tools | Adds, removes, or toggles CSS classes on elements over time |
| loading-states | htmx-ext-loading-states | Adds detailed loading state management to any attribute |
| multi-swap | htmx-ext-multi-swap | Swaps multiple parts of the page from one response |
| response-targets | htmx-ext-response-targets | Routes different HTTP status codes to different targets |
| path-deps | htmx-ext-path-deps | Automatically refreshes elements when a related path changes |
json-enc: Sending JSON Requests
By default, HTMX sends form data as URL-encoded key-value pairs. When your API expects JSON, use the json-enc extension:
<script src="https://unpkg.com/htmx-ext-json-enc@2.0.1/json-enc.js"></script> <form hx-ext="json-enc" hx-post="/api/users" hx-target="#result"> <input type="text" name="username" placeholder="Username"> <input type="email" name="email" placeholder="Email"> <button type="submit">Create User</button> </form>
Without the extension, HTMX sends: username=alice&email=alice@example.com
With json-enc, HTMX sends: {"username":"alice","email":"alice@example.com"}
The server receives a proper JSON body and the Content-Type header is set to application/json automatically.
preload: Near-Instant Navigation
<script src="https://unpkg.com/htmx-ext-preload@2.0.1/preload.js"></script>
<body hx-ext="preload" hx-boost="true">
<nav>
<a href="/home" preload="mouseover">Home</a>
<a href="/products" preload="mouseover">Products</a>
<a href="/about" preload="mouseover">About</a>
</nav>
</body>
Flow:
User moves mouse over "Products" link
↓
preload fires GET /products in the background (hidden)
↓
User clicks the link (200ms later)
↓
Response is already cached → page appears instantly
response-targets: Route Errors to Different Elements
The response-targets extension lets you send different HTTP status codes to different DOM targets. This is ideal for routing error responses to an error panel while successful responses go to the main content area:
<script src="https://unpkg.com/htmx-ext-response-targets@2.0.0/response-targets.js"></script> <form hx-ext="response-targets" hx-post="/submit" hx-target="#success-panel" hx-target-422="#error-panel" hx-target-5*="#server-error-banner"> <input type="text" name="title" required> <button type="submit">Submit</button> </form> <div id="success-panel"></div> <div id="error-panel"></div> <div id="server-error-banner"></div>
| Server Response | Target Used |
|---|---|
| 200 OK | #success-panel |
| 422 Unprocessable | #error-panel |
| 500, 503, etc. | #server-error-banner (wildcard 5*) |
class-tools: CSS Class Scheduling
<script src="https://unpkg.com/htmx-ext-class-tools@2.0.1/class-tools.js"></script> <!-- Add "open" class immediately, remove it after 3 seconds --> <div hx-ext="class-tools" classes="add open:0ms, remove open:3000ms"> This appears, then disappears. </div> <!-- Toggle "active" class every 1 second --> <div hx-ext="class-tools" classes="toggle active:1000ms"> Blinking element </div>
This extension is useful for timed notifications, attention-grabbing animations, and entry/exit effects without JavaScript.
multi-swap: Update Multiple Targets From One Response
<script src="https://unpkg.com/htmx-ext-multi-swap@2.0.0/multi-swap.js"></script> <button hx-ext="multi-swap" hx-post="/update-dashboard" hx-swap="multi:#stats:innerHTML,#chart:outerHTML,#alerts:beforeend"> Refresh Dashboard </button>
The response must contain elements with matching IDs. The extension routes each to its specified target and swap strategy in one operation — similar to OOB swaps but driven by the client's hx-swap attribute instead of the server's response markup.
Writing a Custom Extension
If no existing extension meets your needs, write your own:
<script>
htmx.defineExtension('my-logger', {
onEvent: function(name, event) {
if (name === 'htmx:beforeRequest') {
console.log('[my-logger] Sending:', event.detail.requestConfig.path);
}
if (name === 'htmx:afterRequest') {
console.log('[my-logger] Done:', event.detail.successful);
}
}
});
</script>
<body hx-ext="my-logger">
...all HTMX requests logged to console...
</body>
A custom extension listens to HTMX lifecycle events and runs code at each stage. Extensions can also add new attributes, intercept responses, or modify request configurations.
Key Takeaway
Extensions expand HTMX beyond its core feature set without bloating the base library. Load each extension as a separate script tag after the main HTMX script, then activate it with hx-ext="name" on a container element. Use json-enc for JSON APIs, preload for near-instant navigation, response-targets for error routing, and class-tools for timed CSS class changes. Build custom extensions when the built-in options do not cover a specific need — the extension API is straightforward and well-documented.
