Extending the widget
Register your own React components, page actions, and live data sources so the assistant can render your UI and act inside your app.
Out of the box the assistant answers and calls your API. Registered through window.syncanix, it can go further: render your own components inside the chat, perform actions on the current page, and show live data that keeps itself fresh.
Render your components in the chat
registerComponent hands the assistant a component it may render as part of an answer — an order card, a chart, a form. You give it an id, the component, and a Zod schema for its props; the optional description tells the model when to use it.
import { z } from 'zod';
import { OrderCard } from './OrderCard';
const OrderCardProps = z.object({ orderId: z.string() });
window.syncanix?.registerComponent('OrderCard', OrderCard, OrderCardProps, {
description: 'Shows an order summary',
propDescriptions: { orderId: 'Which order to show' },
});Props the model supplies are validated against your schema before anything renders — a mismatch rejects loudly instead of rendering something half-right.
Give components your app’s context
Registered components render inside the widget, so by default they lack your app’s providers. registerProviders wraps every rendered component in your own wrapper — data client, router, theme — so they behave exactly as they do elsewhere in your app.
window.syncanix?.registerProviders(({ children }) => (
<ApolloProvider client={apolloClient}>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</ApolloProvider>
));Let the assistant act on the page
registerAction exposes a function the assistant can call on the current page — navigate somewhere, refetch a table, open a drawer. Add a Zod parameters schema when the action takes arguments.
window.syncanix?.registerAction('refetch_orders', () => ordersQuery.refetch(), {
description: 'Reload the orders table with the latest data',
});
window.syncanix?.registerAction('go_to_invoice', ({ id }) => navigate(`/invoices/${id}`), {
parameters: z.object({ id: z.string() }),
parameterDescriptions: { id: 'The invoice id to open' },
description: 'Navigate to a specific invoice',
});Scope actions to the page: register when the surface mounts and unregister on teardown, so the assistant only ever sees what is available right now. For actions with a real side effect, pass confirm: true so the user approves before it runs.
Bind live data
registerDataSource lets the assistant show data that stays current: instead of baking a snapshot into its answer, it references your source, and the widget calls your fetcher at render time. Set pollMs (1000 or more) to opt into auto-refresh.
window.syncanix?.registerDataSource(
'open_orders',
async (params) => ({ rows: await api.orders({ status: 'open', ...params }) }),
{ description: 'Current open orders', pollMs: 30000 },
);Tell the assistant where the user is
setContext feeds the current page, plan, cart, or any other host state into the next turn, so answers reflect what the user is looking at. Call it again as the user navigates — the latest value wins.
window.syncanix?.setContext({
user: { id: 'u_1', plan: 'pro' },
page: { path: '/checkout' },
});No React? Use web components
If your app isn’t built on React, the @syncanix/web-components bridge wraps any native custom element so it plugs into the same registerComponent pipeline:
npm install @syncanix/web-componentsimport { defineSyncanixComponent } from '@syncanix/web-components';
import { z } from 'zod';
const StatusBadge = defineSyncanixComponent(
'status-badge',
StatusBadgeElement, // your HTMLElement subclass
z.object({ label: z.string(), tone: z.string() }),
);
window.syncanix?.registerComponent('status-badge', StatusBadge, StatusBadge.propsSchema);Auto-detect page actions
exposePageActions can discover actions from an explicit scope you define — a route map for navigation, plus elements you mark with data-syncanix-action — instead of hand-registering each one. It is gated behind the auto-detect-actions Labs flag, and every discovered action is reported to your dashboard for review before it becomes callable: navigation is confirm-free, marked elements always confirm.