skip to main content
Browse documentation

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-components
import { 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.

Next steps