Flow Studio
Registering a Custom Widget
End-to-end guide to building, bundling, and registering a custom widget — the IWidgetDefinition contract and DI registration.
Step 1: Build the Frontend Bundle
// my-risk-scorer-widget/src/index.ts
// Must expose via window.BizFirstWidgets[widgetId]
(window as any).BizFirstWidgets = (window as any).BizFirstWidgets || {};
(window as any).BizFirstWidgets['my-risk-scorer'] = {
mount(container: HTMLElement, props: RiskScorerProps, callbacks: WidgetCallbacks) {
const root = document.createElement('div');
container.appendChild(root);
// Render your widget
renderRiskScorer(root, props, {
onScore: (score: number, factors: string[]) => {
callbacks.onInteraction('scored', { score, factors, timestamp: new Date().toISOString() });
}
});
},
unmount(container: HTMLElement) {
container.innerHTML = '';
}
};
Step 2: Implement IWidgetDefinition
public class RiskScorerWidgetDefinition : IWidgetDefinition
{
public string WidgetId => "my-risk-scorer";
public string DisplayName => "Risk Scorer";
public string Description => "Interactive risk assessment widget with factor analysis";
public string BundleUrl => "https://cdn.acme.com/widgets/risk-scorer/v1/bundle.js";
public string PropsSchema => @"{
type: 'object',
required: ['applicantId', 'riskFactors'],
properties: {
applicantId: { type: 'string' },
riskFactors: { type: 'array', items: { type: 'object' } },
threshold: { type: 'number', default: 70 }
}
}";
public string InteractionSchema => @"{
type: 'object',
required: ['score'],
properties: {
score: { type: 'number', minimum: 0, maximum: 100 },
factors: { type: 'array', items: { type: 'string' } }
}
}";
public IReadOnlyList<string> InteractionEvents => ["scored", "cancelled"];
public WidgetSandboxPolicy SandboxPolicy => WidgetSandboxPolicy.Standard;
}
// DI registration
services.AddSingleton<IWidgetDefinition, RiskScorerWidgetDefinition>();
Step 3: Host the Bundle
The bundle URL must be publicly accessible (or accessible from the WorkDesk's browser context). Host on CDN, Azure Blob Storage, or an internal static asset server. Ensure proper CORS headers and CSP allow-listing.
Versioning: Include the version in the bundle URL path (
/v1/bundle.js). This enables cache-busting when you release a new version — old tasks already in-flight continue to use the version they were created with until they expire or are completed.