Skip to main content

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 TypeAccess to Symphony APIsRenderingUse Case
text/symphony-jsxFull (direct)Transpiled ReactRich interactive UIs with full platform integration
text/symphony-moduleN/A (imported)Not rendered directlyShared code, styles, and icons imported by other resources
text/html+htmxFull (bridge)Sandboxed iframeServer-rendered HTML with HTMX; shell provided by Symphony
text/html+symphonyFull (bridge)Sandboxed iframeStandard HTML/JS with platform API access
text/htmlNoneSandboxed iframeStatic content, third-party embeds, self-contained apps
text/html;profile=mcp-appMCP protocolSandboxed iframeMCP-compatible applications
text/markdownNoneNative ReactDocumentation 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 a text/symphony-jsx resource 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+symphony and text/html—HTML content is loaded into a sandboxed iframe. For text/html+symphony, a <script> tag referencing the Symphony bridge library is injected into the HTML before loading, which creates the window.symphony API. Plain text/html receives no injection.

  • text/html;profile=mcp-app—HTML content is loaded into a sandboxed iframe managed by the MCP Apps SDK AppFrame component, which establishes an MCP-compatible communication channel.

  • text/markdown—Markdown is rendered directly in the Symphony React component tree using react-markdown with 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