Install
$ agentstack add skill-litestar-org-litestar-skills-litestar-htmx ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
litestar-htmx
Litestar has first-party HTMX support in litestar.plugins.htmx. It exposes HTMXPlugin, HTMXRequest (request-side helpers), HTMXTemplate (template response with HTMX headers), and HTMX-specific response objects (TriggerEvent, Reswap, Retarget, PushUrl, HXLocation, ClientRedirect, ClientRefresh).
This skill is Litestar-specific. For generic HTMX hx-* attributes and patterns that aren't Litestar-bound, refer directly to .
Code Style Rules
- PEP 604 unions:
T | None, neverOptional[T] - Consumer Litestar app modules MAY use
from __future__ import annotations - Async all I/O — handlers are
async def - Return partial HTML, not full pages, from HTMX-targeted endpoints
- Use Jinja2 (or Mako) templates for partials; do not string-concat HTML
Quick Reference
HTMXRequest
HTMXRequest is a Request subclass with HTMX-aware properties:
from litestar import get
from litestar.plugins.htmx import HTMXRequest
@get("/items")
async def list_items(request: HTMXRequest) -> ...:
if request.htmx: # True if HX-Request header present
... # return partial
else:
... # full page
# Other helpers
request.htmx.target # HX-Target header (str | None)
request.htmx.trigger # HX-Trigger header
request.htmx.trigger_name # HX-Trigger-Name header
request.htmx.boosted # HX-Boosted (bool)
request.htmx.current_url # HX-Current-URL
request.htmx.history_restore_request
request.htmx.prompt # HX-Prompt (user input from hx-prompt)
Wire it into the app:
from litestar import Litestar
from litestar.plugins.htmx import HTMXPlugin
app = Litestar(route_handlers=[...], plugins=[HTMXPlugin()])
HTMXTemplate + Partial HTML
Return Jinja partials from handlers:
from litestar import get
from litestar.response import Template
@get("/items")
async def list_items() -> Template:
items = await item_service.get_many()
return Template(template_name="partials/item_list.html", context={"items": items})
For HTMX-targeted endpoints, the template is a fragment (no ` / `), e.g.:
{% for item in items %}
{{ item.name }}
{% endfor %}
Server-driven HTMX Responses
| Response Object | Purpose | | --- | --- | | TriggerEvent(name, after="receive", params={...}) | Sets HX-Trigger / HX-Trigger-After-Swap / HX-Trigger-After-Settle | | ClientRedirect(redirect_to=...) | Sets HX-Redirect — client-side hard redirect | | ClientRefresh() | Sets HX-Refresh: true | | PushUrl(push_url=...) | Sets HX-Push-Url — adds entry to browser history | | Reswap(method="outerHTML") | Sets HX-Reswap — overrides client hx-swap | | Retarget(target="#new") | Sets HX-Retarget — overrides client hx-target | | HXLocation(redirect_to=...) | Sets HX-Location — client-side soft navigation | | HXStopPolling() | Returns 286 — HTMX stops polling on this element |
Example: trigger a custom event after a successful save:
from litestar.plugins.htmx import TriggerEvent
@post("/items")
async def create_item(data: ItemCreate) -> TriggerEvent:
item = await item_service.create(data)
return TriggerEvent(
name="itemCreated",
params={"id": item.id, "name": item.name},
after="receive",
)
OOB (Out-of-Band) Swaps
For a single response that updates multiple regions, render multiple fragments and use hx-swap-oob:
Saved!
New notification!
42
Return the combined HTML as a Template or HTMXTemplate.
CSRF
Use Litestar's CSRF middleware; expose the token to templates as a `` tag and forward it via HTMX:
document.body.addEventListener('htmx:configRequest', (e) => {
e.detail.headers['X-CSRF-Token'] =
document.querySelector('meta[name="csrf-token"]').content;
});
Pairing with litestar-vite (template mode)
For HTMX projects with bundled JS/CSS and HMR, use litestar-vite in template mode. Vite bundles HTMX + extensions + CSS; Litestar returns partials. See ../litestar-vite/SKILL.md and [../litestar-vite/references/modes.md](../litestar-vite/references/modes.md#htmx--template-mode).
{{ vite_hmr() }}
{{ vite('resources/main.js') }}
{% block content %}{% endblock %}
The hx-ext="litestar" client-side templating extension
Activating hx-ext="litestar" (on ` or any enclosing element) unlocks **client-side JSON rendering** via tags. When an HTMX swap uses hx-swap="json", the response body is parsed as JSON and matched against ls-* attributes on descendant ` tags.
This lets you render JSON API responses as HTML without server-side templates — complementary to the partial-HTML pattern.
| Attribute | Purpose | | --- | --- | | ls-for="item in $data" | Iterate over the JSON response array | | ls-key="item.id" | Stable key for list reconciliation | | ls-if="condition" | Render only when truthy | | ls-else | Fallback block for ls-if | | ${expression} | Interpolate JS expression into text content | | :attr="expression" | Dynamic attribute binding | | $data | The raw JSON response body |
Array rendering:
Load
${book.title}
${book.author} • ${book.year}
Single-item rendering (properties on $data accessible directly via prototype inheritance):
${title}
${author} • ${year}
${tag}
Click to load…
When to use this vs server-side partials:
| Case | Approach | | --- | --- | | Data shape simple, rendering trivial, already have JSON endpoint | Client ls-* templating (no HTMXTemplate) | | Complex conditionals, auth-sensitive fields, heavy formatting | Server partials via HTMXTemplate | | Same endpoint serving both JSON (for JS clients) and HTML (for HTMX clients) | Branch on request.htmx; return JSON always and let ls-* render it for HTMX consumers |
Both coexist in one page. The canonical jinja-htmx example in litestar-vite/examples/jinja-htmx/ demonstrates both side by side.
Common HTMX Attributes (quick refresher)
Load
Create
Delete
Polling
Replace element
Append
Boost
Navigate with history
For full HTMX attribute reference, see .
Workflow
Step 1: Wire HTMXRequest
Pass request_class=HTMXRequest to Litestar(...). All handlers can now type-annotate request: HTMXRequest.
Step 2: Decide Page vs Partial Boundaries
For each route, decide:
- Page route — returns full layout (one
Templaterenderingbase.html) - Partial route — returns a fragment used by
hx-get/hx-post
Cluster partial routes under a sub-path like /htmx/... or differentiate by request.htmx.
Step 3: Templates for Partials
Build Jinja2 partials as fragments — no `, no `. Mount your full-page templates separately.
Step 4: Server-driven Behavior
Use TriggerEvent, Refresh, Reswap, Retarget to push behavior from the server. Avoid putting business logic in the client.
Step 5: Pair with litestar-vite (optional)
If the app needs bundled CSS/JS or HMR for non-HTMX assets, add litestar-vite in template or htmx mode. See ../litestar-vite/SKILL.md.
Step 6: CSRF + Auth
Apply Litestar Guards / middleware as usual. Include CSRF token via htmx:configRequest.
Step 7: Test
Use litestar.testing.AsyncTestClient with the HX-Request: true header to exercise partial responses. See ../litestar-testing/SKILL.md.
resp = await client.get("/items", headers={"HX-Request": "true", "HX-Target": "#item-list"})
assert "
## Guardrails
- **Use `litestar.plugins.htmx`**, not generic ASGI patterns — the plugin integrates with Litestar's lifecycle, OpenAPI, and DI. Treat `litestar_htmx` imports as legacy project signals.
- **Register `HTMXPlugin()`** at the app level — handlers shouldn't construct `HTMXRequest` ad-hoc.
- **Return partial HTML for HTMX-targeted routes** — never return a full layout to an `hx-get` target.
- **Use `Template` (Litestar response) — never string-concat HTML** — XSS risk and template caching benefits.
- **CSRF protection applies to HTMX too** — non-GET HTMX requests must include the CSRF token (header preferred).
- **Use server-driven response objects** (`TriggerEvent`, `Reswap`, `Retarget`) rather than ad-hoc JS — keeps logic on the server.
- **Pair with `litestar-vite` only when you need bundled assets / HMR** — pure HTMX with a CDN htmx.min.js works fine without Vite.
- **Don't return JSON to HTMX endpoints** — HTMX expects HTML; JSON breaks `hx-swap` semantics.
- **Test with `HX-Request: true`** to exercise the HTMX path.
### Validation Checkpoint
Before delivering Litestar + HTMX code, verify:
- [ ] `HTMXPlugin()` is registered on the `Litestar(...)` constructor
- [ ] HTMX-targeted routes return Jinja2 fragments (no `` / ``)
- [ ] `Template` response object used (not raw HTML strings)
- [ ] CSRF middleware enabled; token forwarded via `htmx:configRequest`
- [ ] Server-driven behavior uses `TriggerEvent` / `Reswap` / `Retarget` (not ad-hoc JS)
- [ ] Tests assert against partial HTML with `HX-Request: true` header
- [ ] If using `litestar-vite`, mode is `template`
## Example
**Task:** Items page with an HTMX-driven create form, OOB notification, and server-triggered refresh event.
```python
# app/domain/items/controllers.py
from litestar import Controller, get, post
from litestar.response import Template
from litestar.plugins.htmx import HTMXRequest, TriggerEvent
class ItemController(Controller):
path = "/items"
@get("/")
async def index(self) -> Template:
items = await item_service.get_many()
return Template("pages/items.html", context={"items": items})
@get("/list")
async def list_partial(self, request: HTMXRequest) -> Template:
"""Partial used by hx-get on initial load and after create."""
items = await item_service.get_many()
return Template("partials/item_list.html", context={"items": items})
@post("/")
async def create(self, data: ItemCreate) -> TriggerEvent:
item = await item_service.create(data)
return TriggerEvent(
name="itemCreated",
params={"id": item.id, "name": item.name},
after="receive",
)
{% extends "base.html" %}
{% block content %}
Add
{% include "partials/item_list.html" %}
{% endblock %}
{% for item in items %}
{{ item.name }}
{% endfor %}
# tests/test_items.py
async def test_create_item_triggers_event(client):
resp = await client.post(
"/items/",
json={"name": "Widget"},
headers={"HX-Request": "true"},
)
assert resp.status_code == 201
assert "itemCreated" in resp.headers["HX-Trigger"]
References Index
- [litestar-vite Integration](references/litestar_vite.md) — Bundling HTMX + custom JS/CSS with
litestar-vitein template mode.
Cross-References
- [litestar](../litestar/SKILL.md) — Litestar fundamentals (Templates, Controllers, Guards, middleware).
- [litestar-vite](../litestar-vite/SKILL.md) — HTMX + Jinja with Vite-bundled assets.
Official References
Shared Styleguide Baseline
- [General Principles](../litestar-styleguide/references/general.md)
- [Litestar](../litestar-styleguide/references/litestar.md)
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: litestar-org
- Source: litestar-org/litestar-skills
- License: MIT
- Homepage: https://github.com/litestar-org/litestar-skills
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.