> ## Documentation Index
> Fetch the complete documentation index at: https://docs.frogbot.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Dashboard Widgets

> [!WARNING]
>
> The modular dashboard is experimental and its API may change in a future
> release.

Without `admin.dashboard` or a custom dashboard view, the Admin Panel opens on a dashboard with a card for each collection the user can read. Configuring `admin.dashboard` adds your widgets and layouts to that dashboard. An explicit `admin.components.views.dashboard` always takes precedence over the widget configuration.

## Defining Widgets

Register widgets through `admin.dashboard.widgets`. Each widget has a unique slug and a Component Path. Optional fields appear in the widget edit drawer.

```ts theme={"theme":"tokyo-night"}
import { buildConfig } from 'frogbot'

export default buildConfig({
  admin: {
    dashboard: {
      widgets: [
        {
          slug: 'sales-summary',
          Component: '/components/SalesSummary#SalesSummary',
          fields: [
            { name: 'title', type: 'text' },
            {
              name: 'timeframe',
              type: 'select',
              options: ['daily', 'weekly', 'monthly', 'yearly'],
            },
            { name: 'showTrend', type: 'checkbox' },
          ],
          minWidth: 'small',
          maxWidth: 'medium',
        },
      ],
    },
  },
})
```

## Dashboard options

`admin.dashboard` is a `DashboardConfig`:

| Option | Type | Default | Description |
| - | - | - | - |
| `widgets` | `Widget[]` | Required | Widgets users can add to the dashboard. The built-in `collections` widget is always available in addition to these. |
| `defaultLayout` | `WidgetInstance[] \| ({ req }) => WidgetInstance[] \| Promise<WidgetInstance[]>` | One full-width `collections` widget | Layout shown until a user saves their own. The function receives the current `FrogBotRequest`. See [Default Layout](#default-layout). |

Each entry in `widgets` is a `Widget`:

| Option | Type | Default | Description |
| - | - | - | - |
| `slug` | `string` | Required | Unique widget identifier, referenced by `widgetSlug` in layouts. |
| `Component` | `FrogBotComponent` | Required | Component Path, including optional `#ExportName`. The component receives [`WidgetServerProps`](#widget-component-props). |
| `label` | `string \| Record<string, string> \| ({ i18n, t }) => string` | `slug` | Name shown in the Add widget drawer. The widget edit drawer shows a string label and falls back to `slug` otherwise. |
| `fields` | `Field[]` | None | Form fields shown in the edit drawer. Saved values are passed to the component as `widgetData`. |
| `minWidth` | `WidgetWidth` | `'x-small'` | Smallest width offered by the resize controls. |
| `maxWidth` | `WidgetWidth` | `'full'` | Largest width offered by the resize controls. |

`WidgetWidth` is `'x-small' | 'small' | 'medium' | 'large' | 'x-large' | 'full'`.

## Creating a Widget Component

Widgets are Server Components. Their request exposes the FrogBot instance at `req.frogbot`:

```tsx theme={"theme":"tokyo-night"}
import type { WidgetServerProps } from 'frogbot'

export async function UserStats({ req }: WidgetServerProps) {
  const result = await req.frogbot.count({
    collection: 'users',
    overrideAccess: false,
    req,
  })

  return (
    <section className="user-stats">
      <h2>Total users</h2>
      <strong>{result.totalDocs}</strong>
    </section>
  )
}
```

Use the [color tokens](https://docs.frogbot.ai/chat/ui-theming.md#color-tokens) in the widget stylesheet:

```css theme={"theme":"tokyo-night"}
.user-stats {
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  padding: 1rem;
}
```

### Widget component props

| Prop | Type | Default | Description |
| - | - | - | - |
| `req` | `FrogBotRequest` | Required | The current request. The FrogBot instance is available at `req.frogbot`. |
| `widgetSlug` | `string` | Required | Slug of the widget being rendered. [Generated widget types](#generated-widget-types) narrow it to the widget's slug. |
| `widgetData` | `Record<string, unknown>` | Optional | Values saved from the widget's `fields`, or the `data` from its layout item. Generated widget types narrow it to the widget's data shape. |
| `permissions` | `SanitizedPermissions` | Required | The current user's permissions. |
| `user` | `UntypedUser \| null` | Optional | The signed-in user. |
| `locale` | `Locale` | Optional | The current content locale when localization is enabled. |
| `cookies` | `Map<string, string>` | Required | Cookies sent with the request. |

## Default Layout

`defaultLayout` accepts an array or a synchronous or asynchronous callback. The callback receives a `FrogBotRequest`, so it can select a layout using the current user or application data.

```ts theme={"theme":"tokyo-night"}
import { buildConfig } from 'frogbot'

export default buildConfig({
  admin: {
    dashboard: {
      defaultLayout: async ({ req }) => {
        const result = await req.frogbot.count({
          collection: 'orders',
          overrideAccess: false,
          req,
        })

        return [
          {
            widgetSlug: 'sales-summary',
            data: {
              orderCount: result.totalDocs,
              timeframe: 'monthly',
              title: 'Revenue overview',
            },
            width: 'medium',
          },
          {
            widgetSlug: 'user-stats',
            width: 'medium',
          },
        ]
      },
      widgets: [
        {
          slug: 'sales-summary',
          Component: '/components/SalesSummary#SalesSummary',
          fields: [
            { name: 'title', type: 'text' },
            { name: 'timeframe', type: 'text' },
            { name: 'orderCount', type: 'number' },
          ],
          minWidth: 'small',
          maxWidth: 'large',
        },
        {
          slug: 'user-stats',
          Component: '/components/UserStats#UserStats',
        },
      ],
    },
  },
})
```

Each layout item is a `WidgetInstance`:

| Option | Type | Default | Description |
| - | - | - | - |
| `widgetSlug` | `string` | Required | Slug of the registered widget. |
| `width` | `WidgetWidth` | Required | Initial width. It is used as given; `minWidth` and `maxWidth` only limit the resize controls. |
| `data` | `Record<string, unknown>` | None | Initial values passed to the component as `widgetData` and shown in the edit drawer. |

## Generated Widget Types

Run `frogbot generate:types` after registering widget fields. The generated `frogbot-types.ts` includes an interface for each widget, including its data and allowed widths.

```tsx theme={"theme":"tokyo-night"}
import type { WidgetServerProps } from 'frogbot'

import type { SalesSummaryWidget } from '../frogbot-types'

export function SalesSummary({
  widgetData,
}: WidgetServerProps<SalesSummaryWidget>) {
  const title = widgetData?.title ?? 'Sales summary'
  const timeframe = widgetData?.timeframe ?? 'monthly'

  return (
    <section>
      <h2>{title}</h2>
      <p>Timeframe: {timeframe}</p>
    </section>
  )
}
```

Generated types constrain `widgetSlug`, `data`, and `width` in `defaultLayout` to the corresponding widget definition.

## Built-in Widget

The modular dashboard includes a `collections` widget. When you do not provide `defaultLayout`, the dashboard starts with this widget. Its component path is `@frogbotai/ui/admin/rsc#CollectionCards` when you need to reference it explicitly.

## User Customization

From the dashboard editor, users can add widgets, edit widget field values, resize, reorder, and remove widgets. Saving stores the layout in the user's preferences. Canceling discards unsaved edits.

A saved preference takes precedence over `defaultLayout`. Resetting the layout clears that preference and reapplies the current default layout.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.