Skip to main content

Plain HTML Resources

The text/html MIME type renders HTML content in a sandboxed iframe with no access to Symphony platform APIs. This is the simplest resource type—Symphony loads the HTML as-is without any transformation or bridge injection.

When to Use

Use text/html when your content:

  • Is entirely self-contained and does not need to interact with Symphony messaging, storage, or navigation.
  • Embeds a third-party tool or visualization that manages its own backend communication.
  • Displays static content such as reports, charts, or documentation generated by an external process.
  • Uses libraries or frameworks with specific version requirements that may conflict with the versions injected by Symphony for text/symphony-jsx resources, and does not need Symphony API access.

If you need access to NATS messaging, KV storage, or navigation, use text/html+symphony instead. It offers the same dependency freedom with full platform API access.

Registration

html_content = """
<!DOCTYPE html>
<html>
<head>
<title>Status Report</title>
<style>
body { font-family: system-ui, sans-serif; padding: 24px; }
.metric { font-size: 2em; font-weight: bold; }
</style>
</head>
<body>
<h1>Daily Status Report</h1>
<p class="metric">42 jobs completed</p>
<p>Generated at 2025-01-15 08:00 UTC</p>
</body>
</html>
"""

ext.add_resource("ui://my_ext/report", "text/html", html_content)
ext.add_route("/my_ext/report", "ui://my_ext/report")
Resource report = new Resource()
.uri("ui://my_ext/report")
.mimeType(MimeType.TEXT_HTML)
.text(htmlContent);

Sandbox Permissions

The iframe runs with allow-scripts allow-same-origin allow-forms, which means:

  • JavaScript can execute.
  • Forms can submit.
  • The page can access its own origin for local storage, cookies, etc.
  • The page cannot access the parent Symphony frame, NATS connection, or KV storage.

Theming

Plain HTML resources receive color mode updates from the parent frame through the ui-lifecycle-iframe-render-data postMessage protocol. You can listen for these to synchronize your page's appearance:

window.addEventListener('message', (event) => {
if (event.data?.type === 'ui-lifecycle-iframe-render-data') {
const mode = event.data.payload?.renderData?.mode
if (mode === 'dark') {
document.body.classList.add('dark')
} else {
document.body.classList.remove('dark')
}
}
})

Loading External Content

You can load content from external sources within the iframe, as long as the user's browser can reach those sources:

iframe_page = """
<!DOCTYPE html>
<html>
<body style="margin:0; height:100vh;">
<iframe src="https://grafana.example.com/d/abc123"
style="width:100%; height:100%; border:none;">
</iframe>
</body>
</html>
"""

ext.add_resource("ui://my_ext/grafana", "text/html", iframe_page)

Dynamic Content Updates

The resource content can be updated at runtime by the extension:

# Update the resource with fresh content
await ext.update_resource(
"ui://my_ext/report",
"text/html",
generate_report_html()
)

The Symphony UI detects content changes and reloads the iframe automatically.

See Also