Portal Community

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 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>
  );
}
No Direct API Calls from Custom Widgets

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.