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-jsxresources, 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
- User Interfaces—Overview of all resource types
- HTML + Symphony Bridge—HTML with Symphony API access