# How to Define Custom htmx Extensions: A Complete Guide to the Extension API

> Learn to define custom htmx extensions using the Extension API. Master lifecycle hooks like init, onEvent, and transformResponse to extend htmx functionality.

- Repository: [Big Sky Software/htmx](https://github.com/bigskysoftware/htmx)
- Tags: how-to-guide
- Published: 2026-08-30

---

**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`](https://github.com/bigskysoftware/htmx/blob/main/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`](https://github.com/bigskysoftware/htmx/blob/main/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.

```javascript
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 as `htmx:beforeRequest` or `htmx:afterSwap`; return `true` to 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; return `true` to 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.

```html
<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.

```javascript
// 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
    }
  }
});

```

```html
<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.

```javascript
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
  }
});

```

```html
<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`](https://github.com/bigskysoftware/htmx/blob/main/src/htmx.js), lines 5020–5055) walks up the DOM tree, collecting all extensions specified in comma-separated lists.

```html
<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`](https://github.com/bigskysoftware/htmx/blob/main/ws.js)) and Server-Sent Events ([`sse.js`](https://github.com/bigskysoftware/htmx/blob/main/sse.js)) extensions demonstrate advanced usage of `encodeParameters` and `onEvent` to manage persistent connections.

Key files in the `bigskysoftware/htmx` repository:
- [`src/htmx.js`](https://github.com/bigskysoftware/htmx/blob/main/src/htmx.js) – Contains `defineExtension`, the `extensions` registry, and `getExtensions` logic.
- [`www/content/extensions/building.md`](https://github.com/bigskysoftware/htmx/blob/main/www/content/extensions/building.md) – Official documentation covering extension theory and best practices.
- `www/content/api.md#defineExtension` – API reference for `htmx.defineExtension()`.
- `dist/ext/` – Working examples of official extensions.

## Summary

- **Registration** – Use `htmx.defineExtension(name, definition)` to add your extension to the internal registry in [`src/htmx.js`](https://github.com/bigskysoftware/htmx/blob/main/src/htmx.js).
- **Hooks** – Implement any combination of `init`, `getSelectors`, `onEvent`, `transformResponse`, `isInlineSwap`, `handleSwap`, or `encodeParameters` to modify behavior.
- **Activation** – Apply extensions via the `hx-ext` attribute, which supports comma-separated lists for composition.
- **Scope** – Extensions can be scoped to specific elements or automatically activated via `getSelectors`.
- **Fallback** – Always return `false` from `handleSwap` or `onEvent` when 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`](https://github.com/bigskysoftware/htmx/blob/main/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`](https://github.com/bigskysoftware/htmx/blob/main/ws.js) (WebSocket support) and [`sse.js`](https://github.com/bigskysoftware/htmx/blob/main/sse.js) (Server-Sent Events) demonstrate complex implementations using `onEvent` for lifecycle management and `encodeParameters` for custom request formatting.