Skip to content

WIDGET Extensions

WIDGET extensions add standalone components to dashboards and sidebars. Perfect for metrics, tools, and status displays.

  • Dashboard metrics and KPIs
  • Sidebar quick-access tools
  • Status indicators and notifications
  • Global actions and controls
  • Real-time data displays
{
point: 'dashboard.widgets',
plugin: 'my-plugin',
priority: 50,
extension: {
type: ExtensionType.WIDGET,
id: 'my-widget',
component: MyDashboardWidget,
title: 'My Widget',
gridSize: { cols: 2, rows: 1 }
}
}
  • type: ExtensionType.WIDGET
  • id: Unique identifier
  • component: React component
  • title: Widget title
  • gridSize: { cols: number, rows: number } - Dashboard grid size
  • isResizable: Allow user to resize
  • minSize: Minimum grid size
  • maxSize: Maximum grid size
export function MetricsWidget({ context }: WidgetComponentProps) {
const { data: metrics } = trpc.analytics.getMetrics.useQuery({
organizationId: context.organizationId,
});
return (
<Card>
<Title order={4}>Key Metrics</Title>
<Grid>
<Grid.Col span={6}>
<Text c="dimmed">Active Projects</Text>
<Text size="xl" fw={700}>{metrics?.activeProjects}</Text>
</Grid.Col>
<Grid.Col span={6}>
<Text c="dimmed">Revenue</Text>
<Text size="xl" fw={700}>${metrics?.revenue}</Text>
</Grid.Col>
</Grid>
</Card>
);
}
{
point: 'sidebar.widgets',
plugin: 'timer',
priority: 90,
extension: {
type: ExtensionType.WIDGET,
id: 'sidebar-timer',
component: SidebarTimer
}
}
export function SidebarTimer() {
const [activeTimer, setActiveTimer] = useState<TimerState | null>(null);
useEffect(() => {
const updateTimer = () => {
const timers = getTimersFromStorage();
const active = Object.values(timers).find(t => t.isRunning);
setActiveTimer(active);
};
window.addEventListener('storage', updateTimer);
updateTimer();
return () => window.removeEventListener('storage', updateTimer);
}, []);
if (!activeTimer) return null;
return (
<Card shadow="sm" padding="sm">
<Group justify="space-between">
<div>
<Text size="sm" fw={600}>{activeTimer.workUnitName}</Text>
<Text size="xs" c="dimmed">
<ElapsedTime startTime={activeTimer.startTime} />
</Text>
</div>
<ActionIcon onClick={() => stopTimer(activeTimer.id)}>
<IconPlayerStop />
</ActionIcon>
</Group>
</Card>
);
}

Control widget size and layout:

{
type: ExtensionType.WIDGET,
id: 'large-chart',
component: ChartWidget,
gridSize: { cols: 4, rows: 2 },
isResizable: true,
minSize: { cols: 2, rows: 1 },
maxSize: { cols: 6, rows: 4 }
}
  • Keep widgets focused on single purpose
  • Handle loading and error states
  • Respect user’s grid layout preferences
  • Clean up subscriptions and timers
  • Use real-time updates sparingly