Custom Widget
Custom Widgets extend App Studio with your own React components, loaded via Module Federation. They integrate fully with the widget system — config schema, data binding, events, and the Properties Editor.
Architecture: Module Federation
Custom Widgets are loaded at runtime using Webpack Module Federation. Your widget is a separately deployed React application (a "remote") that App Studio (the "host") loads on demand. This means:
- Custom widgets are deployed independently — no changes to App Studio itself
- Widget updates are immediately available to all apps using that widget (on next load)
- The widget runs in the same React context as App Studio — shared dependencies (React, etc.) are not duplicated
Custom Widget Manifest
To register a custom widget, provide a manifest JSON when calling the Widget Registry API:
// POST /api/widget-registry/register
{
"widgetTypeId": "org-chart",
"displayName": "Org Chart",
"description": "Renders an organizational hierarchy chart",
"category": "Custom",
"icon": "sitemap",
// Module Federation endpoint
"federationUrl": "https://widgets.myorg.com/org-chart/remoteEntry.js",
"federationModule": "OrgChartWidget", // exported module name
// Config schema — generates Properties Editor UI
"configSchema": {
"type": "object",
"required": ["dataSource"],
"properties": {
"dataSource": { "type": "string", "title": "Data Source" },
"rootNodeField": { "type": "string", "title": "Root Node Field", "default": "ceo" },
"showAvatars": { "type": "boolean", "title": "Show Avatars", "default": true }
}
},
// Default config
"defaultConfig": {
"showAvatars": true
},
// Events this widget can emit (App Studio will show in Actions tab)
"events": [
{ "name": "onNodeClick", "description": "Fired when a node in the chart is clicked" }
]
}
Widget Component Interface
Your React component receives standardized props from App Studio:
// TypeScript — WidgetRenderProps
interface WidgetRenderProps {
config: Record<string, unknown>; // Resolved config (tokens already evaluated)
context: {
userId: string;
tenantId: string;
roles: string[];
name: string;
};
variables: {
get: (name: string) => unknown;
set: (name: string, value: unknown) => void;
};
actions: {
navigate: (path: string, params?: Record<string, string>) => void;
openModal: (paneId: string) => void;
triggerWorkflow: (processId: string, input: unknown) => Promise<void>;
};
emit: (eventName: string, payload?: unknown) => void; // Fire widget events
}
// Example widget component
export default function OrgChartWidget({ config, emit }: WidgetRenderProps) {
// config.dataSource is already resolved (tokens evaluated)
// config.showAvatars is a boolean
return (
<div>
{/* Your custom rendering */}
<OrgChartRenderer
dataSource={config.dataSource as string}
onNodeClick={(node) => emit("onNodeClick", { nodeId: node.id })}
/>
</div>
);
}
Custom Widget components must not call AIExtension.Service or any backend API directly from the component code. Data should be provided through the config.dataSource pattern — App Studio fetches the data and passes it to the widget. This ensures multi-tenant isolation and caching apply to custom widgets too.