Portal Community

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.