litestar-htmx · git:20260723.07bc8a4 · 2026-07-23 · sha256 fb976e7532e2d17d

litestar-htmx git:20260723.07bc8a4A

Immutable. This exact content is served forever at /api/v1/blob/fb976e7532e2d17d.

---
name: litestar-htmx
description: "Auto-activate for litestar_htmx, HTMXPlugin, HTMXConfig, HTMXRequest, HTMXTemplate, HXLocation, ReplaceUrl, TriggerEvent, HX-* headers, or Litestar partial HTML. Not for generic browser-side HTMX or Litestar Vite JSON templating — those are client concerns."
---

# litestar-htmx

`litestar-htmx` is the standalone Litestar integration for HTMX. Version 0.5.0
ships the `litestar_htmx` import package with request helpers, an optional
application plugin, template responses, and typed HTMX response-header helpers.

## Code Style Rules

- Import the integration from `litestar_htmx`, never
  `litestar.plugins.htmx`; Litestar no longer owns this package's import
  surface.
- Use `HTMXRequest` when handlers inspect HTMX request headers.
- Return template fragments from HTMX endpoints; keep full-page routes and
  fragment routes distinct.
- Use the response classes for `HX-*` headers; do not assemble those headers
  by hand.
- Keep browser-side HTMX extensions separate from this server package.

## Quick Reference

### Configure the plugin

```python
from litestar import Litestar
from litestar_htmx import HTMXPlugin

app = Litestar(
    route_handlers=[...],
    plugins=[HTMXPlugin()],
)
```

`HTMXPlugin()` is the convenience path: it registers the package's request and
response types. Its default `HTMXConfig(set_request_class_globally=True)` sets
`HTMXRequest` only when the application does not already have a request class.

Preserve an existing custom request class by extending `HTMXRequest`:

```python
from litestar_htmx import HTMXRequest


class ApplicationRequest(HTMXRequest):
    """Application request with HTMX helpers."""
```

If the application only needs response helpers, use
`HTMXConfig(set_request_class_globally=False)`. To inspect `request.htmx`,
configure `HTMXRequest` (or a subclass) as the application request class. The
plugin never replaces a request class already present in `AppConfig`.

The plugin itself is optional. Applications can instead set
`request_class=HTMXRequest` directly and return the response subclasses without
registering `HTMXPlugin`.

### Inspect request headers

`request.htmx` is always an `HTMXDetails` object. Its truth value is `True` only
when `HX-Request` is exactly `"true"`.

```python
from litestar import get
from litestar.response import Template
from litestar_htmx import HTMXRequest


@get("/items")
async def list_items(request: HTMXRequest) -> Template:
    template_name = "partials/item-list.html" if request.htmx else "pages/items.html"
    return Template(template_name=template_name, context={"items": []})
```

Available request helpers:

| Property | Source | Result |
| --- | --- | --- |
| `bool(request.htmx)` | `HX-Request` | Whether this is an HTMX request |
| `request.htmx.boosted` | `HX-Boosted` | `bool` |
| `request.htmx.current_url` | `HX-Current-URL` | `str \| None` |
| `request.htmx.current_url_abs_path` | `HX-Current-URL` | Same-origin path, query, and fragment, or `None` |
| `request.htmx.history_restore_request` | `HX-History-Restore-Request` | `bool` |
| `request.htmx.prompt` | `HX-Prompt` | `str \| None` |
| `request.htmx.target` | `HX-Target` | `str \| None` |
| `request.htmx.trigger` | `HX-Trigger` | `str \| None` |
| `request.htmx.trigger_name` | `HX-Trigger-Name` | `str \| None` |
| `request.htmx.triggering_event` | `Triggering-Event` | Decoded JSON value, or `None` |

`triggering_event` is supplied by HTMX's `event-header` extension. Malformed
JSON resolves to `None`. Headers accompanied by
`<Header>-URI-AutoEncoded: true` are URL-decoded before use.

### Return template fragments with HTMX headers

`HTMXTemplate` extends Litestar's `Template`. Annotate handlers with
`Template`, then pass normal `Template` arguments plus HTMX-specific options:

```python
from litestar import get
from litestar.response import Template
from litestar_htmx import HTMXTemplate


@get("/items/fragment")
async def item_list() -> Template:
    return HTMXTemplate(
        template_name="partials/item-list.html",
        context={"items": []},
        push_url=False,
        re_swap="outerHTML",
        re_target="#item-list",
        trigger_event="itemsLoaded",
        params={"count": 0},
        after="receive",
    )
```

`trigger_event`, `params`, and `after` form one event declaration. When
triggering an event, set `after` to `"receive"`, `"settle"`, or `"swap"`.

### Response helper signatures

All helpers are exported from `litestar_htmx` and
`litestar_htmx.response`.

| Helper | Constructor | Behavior |
| --- | --- | --- |
| `HXStopPolling` | `HXStopPolling()` | Returns status `286` |
| `ClientRedirect` | `ClientRedirect(redirect_to)` | Sets `HX-Redirect`; no `Location` header |
| `ClientRefresh` | `ClientRefresh()` | Sets `HX-Refresh: true` |
| `PushUrl` | `PushUrl(content, push_url, **response_kwargs)` | Sets `HX-Push-Url` |
| `ReplaceUrl` | `ReplaceUrl(content, replace_url, **response_kwargs)` | Sets `HX-Replace-Url` |
| `Reswap` | `Reswap(content, method, **response_kwargs)` | Sets `HX-Reswap` |
| `Retarget` | `Retarget(content, target, **response_kwargs)` | Sets `HX-Retarget` |
| `TriggerEvent` | `TriggerEvent(content, name, after, params=None, **response_kwargs)` | Sets the selected `HX-Trigger*` header |
| `HXLocation` | `HXLocation(redirect_to, source=None, event=None, target=None, select=None, swap=None, hx_headers=None, values=None, **response_kwargs)` | Sets JSON in `HX-Location` |

`push_url=False` and `replace_url=False` emit `"false"` to prevent the
corresponding history update.

### Soft navigation with `HXLocation`

Use `HXLocation` for an HTMX navigation request without a full-page reload.
`select` chooses a fragment from the fetched response before it is swapped:

```python
from litestar import post
from litestar_htmx import HXLocation


@post("/items")
async def create_item() -> HXLocation:
    return HXLocation(
        redirect_to="/items",
        source="#create-item",
        event="submit",
        target="#content",
        select="#item-list",
        swap="innerHTML",
        hx_headers={"X-View": "compact"},
        values={"created": "true"},
    )
```

The response uses status `200`, carries `HX-Location`, and removes the ordinary
`Location` header.

### Trigger an event while returning content

`TriggerEvent` requires the response `content`, event `name`, and `after`
phase:

```python
from litestar import post
from litestar_htmx import TriggerEvent


@post("/items")
async def create_item() -> TriggerEvent[str]:
    return TriggerEvent(
        content="<li>Saved</li>",
        name="itemCreated",
        after="swap",
        params={"id": 42},
        media_type="text/html",
    )
```

Prefer `HTMXTemplate` when the content is HTML assembled from application data.

### Litestar Vite is a separate client layer

The standalone package owns Python request parsing and response headers:

```python
from litestar_htmx import HTMXPlugin, HTMXRequest, HTMXTemplate
```

Litestar Vite's `hx-ext="litestar"` JSON templating and CSRF integration come
from the separate `litestar-vite-plugin/helpers` JavaScript export. They are not
installed, registered, or enabled by `HTMXPlugin()`. Use them only when the
project already uses Litestar Vite and needs client-side JSON swaps. See
[Litestar Vite Integration](references/litestar_vite.md).

<workflow>

## Workflow

1. Check the project's installed `litestar-htmx` version and existing request
   class.
2. Register `HTMXPlugin()` or set `request_class=HTMXRequest` directly. Extend
   `HTMXRequest` when the application needs custom request behavior.
3. Separate full-page endpoints from fragment endpoints. Branch on
   `request.htmx` only when one URL intentionally supports both.
4. Render fragments with `Template` or `HTMXTemplate`.
5. Select the narrow response helper matching the required HTMX header.
6. Configure CSRF protection for every state-changing HTMX request.
7. Test the response body, status, and exact `HX-*` header.
8. Add Litestar Vite's client extension only for bundled assets, CSRF header
   injection, or JSON templating.

</workflow>

<guardrails>

## Guardrails

- **Use `litestar_htmx`, never `litestar.plugins.htmx`.** The 0.5.0 package is a
  standalone distribution with its own public import root.
- **Pass every required response-helper argument.** `TriggerEvent` requires
  `content`, `name`, and `after`; `PushUrl`, `ReplaceUrl`, `Reswap`, and
  `Retarget` also require content.
- **Use `select=` on `HXLocation` to choose returned content.** Do not confuse
  it with `target=`, which chooses the receiving element.
- **Do not assume `HTMXPlugin` overrides an existing request class.** It
  preserves a non-null `AppConfig.request_class`.
- **Do not treat `request.htmx` as an optional object.** Test its truth value to
  identify HTMX requests.
- **Do not send a normal redirect for `HXLocation` or `ClientRedirect`.** These
  helpers return `200` with HTMX response headers.
- **Do not attribute `hx-ext="litestar"` to `litestar-htmx`.** That browser
  extension ships with Litestar Vite's npm package.
- **Do not return unsanitized, concatenated HTML.** Render templates so escaping
  and template caching remain intact.

</guardrails>

<validation>

## Validation Checkpoint

- [ ] Imports use `litestar_htmx`, not `litestar.plugins.htmx`
- [ ] `HTMXPlugin()` or `request_class=HTMXRequest` wires request helpers
- [ ] A custom global request class extends `HTMXRequest`
- [ ] Full pages and HTMX fragments have explicit boundaries
- [ ] `TriggerEvent` includes `content`, `name`, and a valid `after` value
- [ ] `ReplaceUrl` uses `replace_url=`, not `push_url=`
- [ ] `HXLocation.select` and `HXLocation.target` serve distinct purposes
- [ ] State-changing HTMX requests include the application's CSRF token
- [ ] Tests assert the exact status, body, and `HX-*` response header
- [ ] Litestar Vite client-extension guidance is identified as a separate layer

</validation>

<example>

## Example

Return a fragment, retarget the swap, prevent a history update, and verify the
HTMX response:

```python
from litestar import Controller, get
from litestar.response import Template
from litestar_htmx import HTMXRequest, HTMXTemplate


class ItemController(Controller):
    path = "/items"

    @get("/")
    async def index(self, request: HTMXRequest) -> Template:
        items = [{"id": 1, "name": "Widget"}]
        if request.htmx:
            return HTMXTemplate(
                template_name="partials/item-list.html",
                context={"items": items},
                re_target="#item-list",
                re_swap="outerHTML",
                push_url=False,
            )
        return Template(template_name="pages/items.html", context={"items": items})
```

```python
async def test_htmx_item_list(client) -> None:
    response = await client.get(
        "/items/",
        headers={"HX-Request": "true", "HX-Target": "item-list"},
    )

    assert response.status_code == 200
    assert response.headers["HX-Retarget"] == "#item-list"
    assert response.headers["HX-Reswap"] == "outerHTML"
    assert response.headers["HX-Push-Url"] == "false"
    assert "<html" not in response.text
```

</example>

## References Index

- **[Litestar Vite Integration](references/litestar_vite.md)** — Keep the
  standalone Python package distinct from Litestar Vite's browser extension.
- **[Litestar](../litestar/SKILL.md)** — Application setup, templates, and
  lifecycle fundamentals.
- **[Litestar Vite](../litestar-vite/SKILL.md)** — Asset bundling, template
  mode, HMR, and the client helper package.
- **[Litestar Testing](../litestar-testing/SKILL.md)** — Async clients and
  application fixtures.

## Official References

- <https://pypi.org/project/litestar-htmx/0.5.0/>
- <https://github.com/litestar-org/litestar-htmx/tree/v0.5.0/litestar_htmx>
- <https://github.com/litestar-org/litestar-htmx/blob/v0.5.0/litestar_htmx/request.py>
- <https://github.com/litestar-org/litestar-htmx/blob/v0.5.0/litestar_htmx/response.py>
- <https://github.com/litestar-org/litestar-htmx/tree/v0.5.0/tests>
- <https://htmx.org/reference/>

## Shared Styleguide Baseline

- [General Principles](../litestar-styleguide/references/general.md)
- [Python](../litestar-styleguide/references/python.md)
- [Litestar](../litestar-styleguide/references/litestar.md)
- [Testing](../litestar-styleguide/references/testing.md)