Symphony JSX Resources
The text/symphony-jsx MIME type provides the richest integration with
Symphony. Resources of this type contain React and TypeScript code
that Symphony transpiles and renders inside a sandboxed iframe with
full access to the platform through SymphonyContext.
When to Use
Use text/symphony-jsx when you want the deepest integration with
Symphony and are comfortable writing React and TypeScript. This
resource type is ideal when:
- You want direct access to the NATS connection, KV storage,
navigation, and theming through
SymphonyContext. - You prefer a zero-build-step workflow where Symphony handles transpilation and module resolution.
- You are building interactive pages with MUI components that should match the Symphony look and feel automatically.
Because Symphony provides the React runtime, MUI, and other core
libraries, your code benefits from automatic theming, routing, and
lifecycle synchronization. However, this also means you cannot control
the versions of those libraries. If your application depends on a
specific version of React or on third-party libraries that are
incompatible with the version provided by Symphony, use
text/html+symphony or
text/html instead—these let you
bundle and control your own dependencies.
How It Works
When Symphony receives a text/symphony-jsx resource, it:
- Wraps the code in an HTML template that includes React, Babel (for in-browser TypeScript transpilation), MUI, and the Symphony client library.
- Resolves any ESM
importstatements by mapping them to the/npmendpoint, which proxies packages from a content delivery network. - Loads the result into a sandboxed iframe with
allow-scripts allow-same-origin allow-formspermissions. - Synchronizes routing, theming, and lifecycle events between the iframe and the parent Symphony UI.
This means your code is standard React—you write components with hooks, import third-party libraries, and use JSX—but you do not need a build step. Symphony handles transpilation and module resolution. Your code runs using the versions of React, MUI, and other libraries provided by Symphony.
Registration
page = """
import { useContext } from 'react'
import { SymphonyContext } from '@symphony'
export default function MyPage() {
const ctx = useContext(SymphonyContext)
return <h1>Hello from {ctx.symphonyInfo.name}</h1>
}
"""
ext.add_resource("ui://my_ext/home", "text/symphony-jsx", page)
ext.add_route("/my_ext", "ui://my_ext/home")
Resource home = new Resource()
.uri("ui://my_ext/home")
.mimeType(MimeType.TEXT_SYMPHONY_JSX)
.text(getTemplate("home.tsx"));
SymphonyContext
Every text/symphony-jsx component has access to SymphonyContext,
which provides:
import { useContext } from 'react'
import { SymphonyContext } from '@symphony'
const ctx = useContext(SymphonyContext)
| Property | Type | Description |
|---|---|---|
nc | NatsConnection | undefined | NATS WebSocket connection |
symphonyInfo | SymphonyInfo | Platform name, version, and build info |
accessToken | string | undefined | OAuth bearer token |
accountIdentifier | string | undefined | Current user's account ID |
setTitle | (title) => void | Set the page title in the Symphony navigation |
NATS Messaging
Use symphonyContext.nc for request/reply communication with backend
services:
import { useContext, useEffect, useState } from 'react'
import { SymphonyContext } from '@symphony'
const REQUEST_TIMEOUT = 5000
export default function DataView() {
const { nc, setTitle } = useContext(SymphonyContext)
const [items, setItems] = useState([])
useEffect(() => {
setTitle({ "/my_ext": "Data View" })
}, [setTitle])
useEffect(() => {
if (!nc) return
const load = async () => {
const reply = await nc.request(
'cirata.extensions.my_ext.list',
JSON.stringify({ limit: 100 }),
{ timeout: REQUEST_TIMEOUT }
)
const text = new TextDecoder('utf-8').decode(reply.data)
setItems(JSON.parse(text))
}
load()
}, [nc])
return (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
)
}
Always use nc.request() to communicate with backend services. Do not
use HTTP fetch() to call extension services directly—extension
services are NATS microservices, not HTTP endpoints.
KV Storage
Access JetStream KV buckets for persistent state:
import { useContext, useEffect, useState } from 'react'
import { SymphonyContext } from '@symphony'
import { Kvm } from '@nats-io/kv'
export default function Settings() {
const { nc } = useContext(SymphonyContext)
const [config, setConfig] = useState(null)
useEffect(() => {
if (!nc) return
const load = async () => {
const kvm = new Kvm(nc)
const bucket = await kvm.open('my_ext_config', { allow_direct: true })
const entry = await bucket.get('settings')
if (entry) setConfig(JSON.parse(entry.string()))
}
load()
}, [nc])
const save = async () => {
if (!nc) return
const kvm = new Kvm(nc)
const bucket = await kvm.create('my_ext_config', { allow_direct: true })
await bucket.put('settings', JSON.stringify(config))
}
return (
<div>
<pre>{JSON.stringify(config, null, 2)}</pre>
<button onClick={save}>Save</button>
</div>
)
}
Importing Third-Party Libraries
Any ESM package available on npm can be imported directly. Symphony
resolves imports through its /npm endpoint:
import { DataGrid } from '@mui/x-data-grid'
import { Chart } from 'chart.js'
import dayjs from 'dayjs'
MUI components are available by default and share theming with the parent Symphony UI, including automatic light/dark mode synchronization.
Importing from Extension Modules
Extensions can share code between resources by registering
text/symphony-module resources and importing from them:
# Register a shared module (not routable—only importable)
ext.add_resource("ui://my_ext/common", "text/symphony-module", common_code)
# Register a page that imports from the module
ext.add_resource("ui://my_ext/home", "text/symphony-jsx", page_code)
In the page resource:
import { formatDate, cardStyle } from '@symphony/extension/my_ext/common'
import { Box, Typography } from '@mui/material'
export default function Home() {
return (
<Box sx={cardStyle}>
<Typography>Updated: {formatDate(Date.now())}</Typography>
</Box>
)
}
The import path follows the pattern
@symphony/extension/<prefix>/<module>, which maps to the resource
URI ui://<prefix>/<module>. Modules can export functions, constants,
style objects, and React components. They can import from npm packages
and @symphony APIs just like page resources.
Page Title
Set the page title displayed in the Symphony breadcrumb navigation
using setTitle. The parameter is a map from route path to label:
useEffect(() => {
symphonyContext.setTitle({
"/": "Home",
"/my_ext": "My Extension",
"/my_ext/settings": "Settings"
})
}, [symphonyContext.setTitle])
Child Routes
Pages can define nested routes for sub-navigation:
ext.add_route("/my_ext", "ui://my_ext/home", routes={
"/my_ext/detail": {"uri": "ui://my_ext/detail"},
"/my_ext/settings": {"uri": "ui://my_ext/settings"}
})
Each child route maps to a separate resource that renders as a nested
component within the parent page's <Outlet />.
Limitations
- No build step—Code is transpiled in the browser with Babel. This means some advanced TypeScript features (decorators, const enums) may not be supported.
- No relative navigation—React Router's relative navigation
(e.g.,
navigate(-1)) is not supported. Use absolute paths. - Module resolution—ESM packages served through the
/npmCDN proxy,@symphonyplatform APIs, and@symphony/extension/module resources are available. CommonJS-only packages will not work.
See Also
- User Interfaces—Overview of all resource types
- Platform Functionality—Routes, menus, widgets, and services