| 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
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:
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".
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:
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:
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:
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:
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.
Workflow
- Check the project's installed
litestar-htmx version and existing request
class.
- Register
HTMXPlugin() or set request_class=HTMXRequest directly. Extend
HTMXRequest when the application needs custom request behavior.
- Separate full-page endpoints from fragment endpoints. Branch on
request.htmx only when one URL intentionally supports both.
- Render fragments with
Template or HTMXTemplate.
- Select the narrow response helper matching the required HTMX header.
- Configure CSRF protection for every state-changing HTMX request.
- Test the response body, status, and exact
HX-* header.
- Add Litestar Vite's client extension only for bundled assets, CSRF header
injection, or JSON templating.
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.
Validation Checkpoint
Example
Return a fragment, retarget the swap, prevent a history update, and verify the
HTMX response:
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})
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
References Index
- Litestar Vite Integration — Keep the
standalone Python package distinct from Litestar Vite's browser extension.
- Litestar — Application setup, templates, and
lifecycle fundamentals.
- Litestar Vite — Asset bundling, template
mode, HMR, and the client helper package.
- Litestar Testing — Async clients and
application fixtures.
Official References
Shared Styleguide Baseline