Skip to main content

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-theme attribute.

How It Works

When Symphony renders a text/html+htmx resource, it:

  1. Parses the resource text as a JSON configuration object.
  2. Generates a complete HTML shell document that includes the HTMX library, the Symphony bridge, and integration scripts.
  3. 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.
  4. Injects an htmx:configRequest handler that attaches the user's Authorization: Bearer token to every HTMX request.
  5. Synchronizes the Symphony theme by setting data-theme on the <html> element ("light" or "dark").
  6. 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

FieldTypeRequiredDescription
prefixstringYesExtension's NATS service name (used to construct the base URL)
initialPathstringYesRelative path for the initial hx-get request on page load
titlestringNoPage title
htmxExtensionsstring[]NoHTMX 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>
warning

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

Aspecttext/html+htmxtext/html+symphonytext/symphony-jsx
Content modelServer-rendered HTML fragmentsClient-side HTML/JSReact components
Resource textJSON configFull HTML documentJSX/TSX source
HTMX includedYes (automatic)No (manual)No
Auth injectionAutomatic for HTMXManual via bridgeAutomatic
Theme syncdata-theme attributeManual via bridgeMUI automatic
Build stepNoneOptionalNone

See Also