Logo VitNode

PluginsAdmin Control Panel

Dashboard Widgets

Add a configurable AdminCP dashboard widget from a VitNode plugin with sizing, permissions, and optional settings.

Dashboard widgets are AdminCP plugin extensions. Define the component and register it in the same plugin factory that owns the feature; the host dashboard then discovers it automatically.

Build the widget component

plugins/site-notes/src/views/admin/widgets/stats-widget.tsx
import type { AdminDashboardWidgetProps } from '@vitnode/core/lib/plugin'

export const StatsWidget = ({ settings }: AdminDashboardWidgetProps) => (
  <div className="flex flex-col gap-1 p-2">
    <span className="text-muted-foreground text-sm">Published notes</span>
    <strong className="text-3xl tabular-nums">
      {String(settings.total ?? 42)}
    </strong>
  </div>
)

Register it in the existing plugin factory

plugins/site-notes/src/config.tsx
import { BarChart3Icon } from 'lucide-react'

import { StatsWidget } from './views/admin/widgets/stats-widget'

export const siteNotesPlugin = () =>
  buildPlugin({
    admin: {
      dashboard: {
        widgets: [
          {
            component: StatsWidget,
            defaultEnabled: true,
            defaultRows: 1,
            defaultSpan: 1,
            icon: <BarChart3Icon />,
            id: 'stats',
          },
        ],
      },
    },
    messages,
    pluginId: '@acme/site-notes',
    routes,
  })

The full widget id is namespaced by the plugin, so stats will not collide with another plugin’s idea of a stats card. A good thing—statistics are dramatic enough already.

Add a permission or settings UI when needed

Add permission to hide a widget from staff who should not see it. Add settingsComponent when administrators need to save preferences; both stay in the plugin alongside the widget.

plugins/site-notes/src/config.tsx
{
  component: StatsWidget,
  permission: { module: 'site_notes', permission: 'can_view_stats' },
  settingsComponent: StatsSettings,
  id: 'stats',
}

Widget options

OptionWhat it controls
defaultSpan / defaultRowsInitial grid size from 1 to 3 columns or rows.
minSpanNarrowest allowed width.
defaultEnabledWhether a new dashboard receives the widget.
allowMultipleWhether an admin may place more than one copy.
permissionStaff permission required to see the widget.
settingsComponentPlugin form shown in the widget's panel while the dashboard is being edited.

How admins arrange them

Your widget does not need any code for this - it is what the dashboard does with every widget. Press Edit layout and the Widgets panel slides in beside the grid.

To…Do this
Add a widgetClick it in the panel to put it at the end, or drag it onto the grid. While you drag, the grid opens a gap the widget's real size, right where it will land.
Change your mindDrag it back over the panel and let go. Nothing is added, and the card glides back to where it came from.
Move a widgetDrag the card from anywhere on it, or Tab to it and use Space and the arrow keys. The spot it came from turns into a dashed slot.
Resize a widgetGrab the little bracket in the card's bottom corner and pull. Sideways snaps the width between 1 and 3 columns, down and up snap the height between 1 and 3 rows, and the other widgets glide out of the way. A dashed outline follows your pointer so you can see where you are pulling. From the keyboard, Tab to the corner and use the arrow keys.
Edit a widgetClick the card (or Tab to it and press Enter). The panel switches to that widget: a width switch, its own settings form if it has one, and a bin in the header. Save settings stores the settings straight away and leaves the rest of your unsaved arrangement alone. The arrow at the top goes back to the list.
Remove a widgetThe bin on the card's toolbar, or in its panel.
FinishThe panel footer says whether anything changed. Save keeps the arrangement; Cancel throws it away and leaves edit mode.

New widgets fade in and scroll into view, and screen readers hear where a widget was picked up and dropped in the admin's own language. With reduce motion turned on in the operating system, all of it happens instantly.

The drop gap uses defaultSpan and defaultRows, so pick them to match what the widget looks like the moment it is added.