HTML + HTMX Resources
The text/html+htmx MIME type lets you build server-rendered HTML
extensions using HTMX. Symphony provides an
HTMX application shell with the library pre-loaded, automatic auth
token injection, and theme synchronization. Your extension only needs
to supply NATS microservice endpoints that return HTML fragments.
When to Use
Use text/html+htmx when you want to build extension UIs using
server-rendered HTML with HTMX for dynamic content loading. This
resource type is ideal when:
- You prefer server-side rendering over client-side JavaScript frameworks.
- Your extension already has NATS endpoints that can return HTML fragments alongside JSON responses.
- You want the simplicity of returning HTML from your backend rather than building a client-side application.
- You want to use CSS frameworks like DaisyUI or Tailwind CSS with
theme-aware styling via the
data-themeattribute.
How It Works
When Symphony renders a text/html+htmx resource, it:
- Parses the resource text as a JSON configuration object.
- Generates a complete HTML shell document that includes the HTMX library, the Symphony bridge, and integration scripts.
- Sets a
<base>URL pointing to the extension's API proxy endpoint (/api/v1/extension/{prefix}/), so that relative HTMX URLs resolve to the extension's NATS service automatically. - Injects an
htmx:configRequesthandler that attaches the user'sAuthorization: Bearertoken to every HTMX request. - Synchronizes the Symphony theme by setting
data-themeon the<html>element ("light"or"dark"). - Loads the result into a sandboxed iframe.
When HTMX fires a request (e.g., hx-get="pages/home"), it resolves
against the <base> URL and reaches the extension's NATS endpoint
via Symphony's built-in extension proxy. The endpoint returns an
HTML fragment, and HTMX swaps it into the DOM.
Registration
The resource text is a JSON configuration object, not HTML:
ext.add_htmx_resource("ui://my_ext/home", "pages/home")
ext.add_route("/my_ext", "ui://my_ext/home")
Or equivalently using the generic add_resource:
import json
config = json.dumps({
"prefix": "my_ext",
"initialPath": "pages/home"
})
ext.add_resource("ui://my_ext/home", "text/html+htmx", config)
ext.add_route("/my_ext", "ui://my_ext/home")
ext.AddHtmxResource("ui://my_ext/home", "pages/home")
ext.AddRoute("/my_ext", "ui://my_ext/home")
ext.add_htmx_resource("ui://my_ext/home", "pages/home").await;
ext.add_route("/my_ext", "ui://my_ext/home").await;
Configuration Fields
| Field | Type | Required | Description |
|---|---|---|---|
prefix | string | Yes | Extension's NATS service name (used to construct the base URL) |
initialPath | string | Yes | Relative path for the initial hx-get request on page load |
title | string | No | Page title |
htmxExtensions | string[] | No | HTMX extensions to load (e.g., ["json-enc", "idiomorph"]) |
Supported HTMX extensions: json-enc, idiomorph,
response-targets, head-support.
Endpoint Pattern
Your NATS endpoints should detect the HX-Request header and return
HTML fragments when present. Symphony's extension proxy forwards
all headers, including HTMX headers like HX-Request, HX-Target,
and HX-Trigger.
The endpoint response should use the wrapped JSON format:
import json
async def handle_home(msg):
html = """
<h1>Dashboard</h1>
<button hx-get="pages/details" hx-target="#content" hx-swap="innerHTML">
View Details
</button>
<div id="content"></div>
"""
response = json.dumps({
"body": html,
"statusCode": 200,
"contentType": "text/html"
})
await msg.respond(response.encode())
Because the shell sets <base href="/api/v1/extension/{prefix}/">,
relative URLs in HTMX attributes resolve to the extension's proxy
automatically. For example, hx-get="pages/details" resolves to
/api/v1/extension/my_ext/pages/details.
Theme Support
The shell sets data-theme="light" or data-theme="dark" on the
<html> element and keeps it synchronized with Symphony's
theme. This integrates with DaisyUI's theme system and can be used
with custom CSS:
[data-theme="dark"] {
--bg: #1a1a2e;
--fg: #e0e0e0;
}
[data-theme="light"] {
--bg: #ffffff;
--fg: #333333;
}
body {
background: var(--bg);
color: var(--fg);
}
To load CSS frameworks in your HTML fragments, include them as
<link> or <style> tags in your responses. Common frameworks
can be loaded from Symphony's npm proxy:
<link rel="stylesheet" href="/npm/daisyui@5/dist/full.min.css">
<script src="/npm/@tailwindcss/browser@4"></script>
The /npm/ proxy is only available when the deployment's
dependency resolution mode
is Proxy or Mixed. In Bundle-only mode, /npm/ returns
404 and references like the above will fail. For deployments that
must run in Bundle-only mode, ship the framework as part of the
extension's UI dependency bundle,
or vendor the CSS into a static resource served by the extension's
NATS service endpoints.
Bridge API
The full window.symphony bridge API is available in text/html+htmx
resources. See
HTML + Symphony Bridge
for the complete API reference, including NATS messaging, KV storage,
navigation, and context access.
The shell pre-configures auth token injection, so you do not need to
manage tokens manually for HTMX requests. For non-HTMX requests (e.g.,
fetch calls), use the bridge:
symphony.ready.then(async () => {
const token = await symphony.getAccessToken()
const resp = await fetch('/api/v1/extension/my_ext/data', {
headers: { 'Authorization': 'Bearer ' + token }
})
})
Differences from Other Resource Types
| Aspect | text/html+htmx | text/html+symphony | text/symphony-jsx |
|---|---|---|---|
| Content model | Server-rendered HTML fragments | Client-side HTML/JS | React components |
| Resource text | JSON config | Full HTML document | JSX/TSX source |
| HTMX included | Yes (automatic) | No (manual) | No |
| Auth injection | Automatic for HTMX | Manual via bridge | Automatic |
| Theme sync | data-theme attribute | Manual via bridge | MUI automatic |
| Build step | None | Optional | None |
See Also
- User Interfaces---Overview of all resource types
- HTML + Symphony Bridge---
window.symphonyAPI reference - Platform Functionality---Routes, menus, and services