User Interfaces
Extensions can provide user interface content to Symphony by registering resources—named pieces of UI content identified by a URI. Each resource has a MIME type that determines how Symphony renders it and what capabilities are available to it.
Registering Resources
Resources are registered with add_resource (Python), createResource
(Java), or AddResource (Go), specifying a URI, MIME type, and
content:
ext.add_resource("ui://my_ext/home", "text/html+symphony", html_content)
Resource home = new Resource()
.uri("ui://my_ext/home")
.mimeType(MimeType.TEXT_HTML_SYMPHONY)
.text(htmlContent);
ext.AddResource("ui://my_ext/home", "text/html+symphony", htmlContent)
Resources are then mapped to routes (for pages) or registered as widgets:
ext.add_route("/my_ext", "ui://my_ext/home")
ext.add_widget("ui://my_ext/status", "Status", "Real-time status")
Available Resource Types
Each MIME type provides a different balance of capability, isolation, and development approach. The following table summarizes the options:
| MIME Type | Access to Symphony APIs | Rendering | Use Case |
|---|---|---|---|
text/symphony-jsx | Full (direct) | Transpiled React | Rich interactive UIs with full platform integration |
text/symphony-module | N/A (imported) | Not rendered directly | Shared code, styles, and icons imported by other resources |
text/html+htmx | Full (bridge) | Sandboxed iframe | Server-rendered HTML with HTMX; shell provided by Symphony |
text/html+symphony | Full (bridge) | Sandboxed iframe | Standard HTML/JS with platform API access |
text/html | None | Sandboxed iframe | Static content, third-party embeds, self-contained apps |
text/html;profile=mcp-app | MCP protocol | Sandboxed iframe | MCP-compatible applications |
text/markdown | None | Native React | Documentation and help pages |
Choosing a Resource Type
Use text/symphony-jsx when
You want the richest integration with Symphony. Your UI code runs
as React components with direct access to the NATS connection, KV
storage, navigation, and theming through SymphonyContext. This is the
most capable option but requires writing React/TypeScript.
Use text/symphony-module when
You want to share code between multiple text/symphony-jsx resources.
Module resources export helper functions, style objects, or React
components that other resources import using
import { ... } from '@symphony/extension/<prefix>/<module>'.
Modules are not rendered directly and cannot be routed to—they exist
only to be imported by page and widget resources.
Use text/html+htmx when
You want to build your extension UI using server-rendered HTML with HTMX for dynamic content loading. Symphony provides the HTMX shell automatically—your extension only needs NATS endpoints that return HTML fragments. This is ideal for extensions that prefer server-side rendering over client-side frameworks, or that already have service endpoints capable of returning HTML.
Use text/html+symphony when
You want to use standard HTML, CSS, and JavaScript while still
accessing Symphony platform APIs. The window.symphony bridge
provides the same NATS messaging, KV storage, and navigation
capabilities as text/symphony-jsx, but through a postMessage-based
API. This is ideal when you prefer vanilla web development, want to use
a non-React framework, or need to integrate existing web applications.
Use text/html when
Your content is self-contained and does not need to interact with Symphony APIs. The HTML runs in a sandboxed iframe with no bridge injected. Use this for static content, embedded third-party tools, or applications that communicate with their own backend independently.
Use text/html;profile=mcp-app when
You are building an application that follows the Model Context Protocol (MCP) Apps SDK conventions. The content runs in a sandboxed iframe with an MCP-compatible bridge that supports tool calls, link navigation, and host messaging.
Use text/markdown when
You are providing documentation or help content. Markdown is rendered
natively in the Symphony React tree (not in an iframe), which means
it matches the look and feel of the built-in help system. Template
variables like Cirata Symphony and Symphony are substituted
automatically.
How Resources Are Rendered
Symphony processes each resource type differently before presenting it to the user:
-
text/symphony-jsx—The JSX/TSX code is wrapped in an HTML template that includes React, Babel, MUI, and the Symphony library. The template handles routing, theming, and lifecycle synchronization with the parent frame. The result is loaded into a sandboxed iframe as a self-contained React application. -
text/symphony-module—Module code is not rendered directly. When atext/symphony-jsxresource imports from a module using@symphony/extension/..., Symphony transpiles the module, creates a loadable URL, and rewrites the import to point at it. This happens transparently during the Babel compilation step. -
text/html+htmx—The JSON config is used to generate an HTML shell with the HTMX library, the Symphony bridge, auth token injection, and a<base>URL pointing to the extension's proxy endpoint. On load, HTMX fetches the initial content from the extension's NATS endpoint. All subsequent HTMX requests are authenticated automatically. -
text/html+symphonyandtext/html—HTML content is loaded into a sandboxed iframe. Fortext/html+symphony, a<script>tag referencing the Symphony bridge library is injected into the HTML before loading, which creates thewindow.symphonyAPI. Plaintext/htmlreceives no injection. -
text/html;profile=mcp-app—HTML content is loaded into a sandboxed iframe managed by the MCP Apps SDKAppFramecomponent, which establishes an MCP-compatible communication channel. -
text/markdown—Markdown is rendered directly in the Symphony React component tree usingreact-markdownwith MUI styling. It is not isolated in an iframe.
Security Model
All iframe-based resource types (text/symphony-jsx,
text/html+symphony, text/html, text/html;profile=mcp-app) run
with the sandbox permissions allow-scripts allow-same-origin allow-forms. Resources that have access to Symphony APIs
(text/symphony-jsx and text/html+symphony) operate with the same
permissions as the logged-in user—NATS enforces capability-based
access control on all messaging operations regardless of the resource
type.
See Also
- Platform Functionality—Routes, pages, menus, widgets, and services
- Languages and Libraries—Python, Java, Go, and Rust development libraries
- Build Your First Extension—Step-by-step tutorial