HTMX Extensions — Complete Guide with Examples
In this tutorial, you'll learn about HTMX extensions. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
HTMX extensions add optional features like JSON encoding, client-side template rendering, path dependency tracking, class manipulation, and multi-element swapping without modifying the core library.
What You'll Learn
By the end of this tutorial, you'll use the JSON-enc extension, client-side-templates with mustache/handlebars, class-tools for CSS class manipulation, multi-swap for updating multiple targets, and create custom extensions.
Why It Matters
Extensions let you add features without waiting for HTMX core updates. The extension ecosystem covers common patterns: JSON APIs instead of HTML, template rendering on the client, and complex DOM updates.
Real-World Use
Doda Browser uses the client-side-templates extension to render JSON API responses with Handlebars templates on the client, avoiding the need for a server-side HTML rendering layer for non-critical UI components.
Where This Fits in Your Learning Path
flowchart LR
A["Hyperscript"] --> B["**HTMX Extensions**"]
B --> C["HTMX Project"]
C --> D["Production HTMX Apps"]
style B fill:#3b82f6,stroke:#2563eb,color:#fff
style A fill:#e2e8f0,stroke:#94a3b8
style D fill:#e2e8f0,stroke:#94a3b8
Enabling Extensions
Extensions are loaded via script tags and enabled with the hx-ext attribute.
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/htmx.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/ext/json-enc/htmx-json-enc.js"></script>
<form hx-post="/api/data" hx-ext="json-enc">
<input name="name" value="Alice">
<input name="age" value="30">
<button type="submit">Submit as JSON</button>
</form>
Expected output: The form data is sent as JSON instead of URL-encoded form data.
Client-Side Templates Extension
Render JSON responses with client-side templates.
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/ext/client-side-templates/htmx-client-side-templates.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mustache@4/mustache.min.js"></script>
<div hx-ext="client-side-templates">
<button hx-get="/api/users"
hx-target="#user-list"
mustache-template="user-template">
Load Users
button>
<div id="user-list"></div>
<template id="user-template">
{{#users}}
<div class="user-card">
<h3>{{name}}</h3>
<p>{{email}}</p>
</div>
{{/users}}
</template>
</div>
Expected output: The JSON response is rendered using the Mustache template and inserted into the target.
Class-Tools Extension
Manipulate CSS classes declaratively.
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/ext/class-tools/htmx-class-tools.js"></script>
<div hx-ext="class-tools">
<div classes="toggle .highlight :500ms">
Toggles highlight every 500ms
</div>
<div classes="add .fade-in :load">
Fades in on load
</div>
<button classes="add .pulse :click, remove .pulse :500ms">
Pulse on click
</button>
</div>
<style>
.highlight { background: #ffeb3b; }
.fade-in { animation: fadeIn 0.5s ease-in; }
.pulse { transform: scale(1.05); transition: transform 0.2s; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
</style>
Expected output: Elements automatically add and remove CSS classes based on time or events.
Multi-Swap Extension
Update multiple targets from a single response.
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/ext/multi-swap/htmx-multi-swap.js"></script>
<button hx-get="/api/dashboard-update"
hx-ext="multi-swap"
hx-swap="multi:#stats,#notifications,#sidebar">
Refresh Dashboard
</button>
<div id="stats">Stats loading...</div>
<div id="notifications">Notifications loading...</div>
<div id="sidebar">Sidebar loading...</div>
Expected output: The server response contains elements with matching IDs. Each one is swapped into its target.
Creating a Custom Extension
Create your own HTMX extension.
htmx.defineExtension('confirm', {
onEvent: function(name, evt) {
if (name === 'htmx:beforeRequest') {
if (!confirm('Are you sure?')) {
evt.preventDefault()
}
}
}
})
<button hx-delete="/api/items/1"
hx-ext="confirm"
hx-target="#item-1"
hx-swap="outerHTML">
Delete (with confirmation)
</button>
Expected output: Before the delete request, the browser shows a confirm dialog. Cancel prevents the request.
Common Mistakes
1. Forgetting to load the extension script
Extensions require separate script tags. Missing scripts silently fail. Check the console for 404 errors.
2. Not adding hx-ext attribute
The extension must be enabled with hx-ext on the element or a parent. Without it, the extension code doesn't run.
3. Using conflicting extensions
Some extensions modify the same HTMX behavior. Test combinations in isolation first.
4. Not matching template IDs with mustache-template
The template ID in the attribute must exactly match the template element's ID. Mismatches silently fail.
5. Creating extensions that modify core behavior
Custom extensions should add functionality, not override core HTMX behavior. Overriding can break future updates.
Practice Questions
How do you enable an extension on an element? Add the hx-ext attribute with the extension name: hx-ext="json-enc".
What does the JSON-enc extension do? It serializes form data as JSON (Content-Type: application/json) instead of URL-encoded.
How does client-side-templates work? It renders JSON API responses using a client-side template library like Mustache or Handlebars.
What does the class-tools extension do? It adds and removes CSS classes on elements based on time intervals or events.
How do you create a custom extension? Use htmx.defineExtension with a name and lifecycle event handlers.
Challenge
Create a custom extension called "debounce" that adds a configurable delay to HTMX requests triggered by input events, preventing rapid-fire requests.
FAQ
Mini Project
Build a JSON-powered user list using multiple extensions. Use JSON-enc for form submission, client-side-templates with Mustache for rendering, class-tools for hover effects, and multi-swap to update the list and stats simultaneously.
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/htmx.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/ext/json-enc/htmx-json-enc.js"></script>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/ext/client-side-templates/htmx-client-side-templates.js"></script>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/ext/class-tools/htmx-class-tools.js"></script>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/ext/multi-swap/htmx-multi-swap.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mustache@4/mustache.min.js"></script>
<div hx-ext="json-enc, client-side-templates, class-tools, multi-swap">
<form hx-post="/api/users"
hx-target="#user-list"
mustache-template="user-row"
hx-swap="beforeend"
hx-on::after-request="this.reset()">
<input name="name" placeholder="Name" required>
<input name="email" placeholder="Email" required>
<button type="submit">Add User</button>
</form>
<div id="user-list" hx-get="/api/users" hx-trigger="load" mustache-template="user-list-template"></div>
<div id="user-count" hx-get="/api/users/count" hx-trigger="load"></div>
<template id="user-list-template">
{{#users}}
<div class="user-row" classes="add .hover-bg :mouseenter, remove .hover-bg :mouseleave">
<span>{{name}}</span>
<span>{{email}}</span>
</div>
{{/users}}
</template>
</div>
What's Next
Build a complete HTMX application:
| Tutorial | What You'll Learn |
|---|---|
| HTMX Project | Build a complete production-ready HTMX application |
| HTMX Advanced | WebSockets, SSE, and advanced HTMX patterns |
Related topics: template engines (Mustache, Handlebars), plugin architecture patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro