Skip to main content

Plugin Management Pages

The web dashboard provides a dedicated plugin management area under the /plugins route group. Users can browse available plugins, enable or disable them, configure settings, and manage OAuth connections. All plugin UI lives in src/components/plugins/ with server pages in src/app/[locale]/(dashboard)/plugins/.

Route Structure

/[locale]/(dashboard)/plugins/
page.tsx # Plugin list page (server component)
[pluginId]/
page.tsx # Plugin detail/settings page (server component)

Plugin List Page

Route: /plugins File: src/app/[locale]/(dashboard)/plugins/page.tsx

The server component fetches all plugins and passes them to the client-side PluginsList component.

const pluginsData = await pluginsAPI.list();

<PluginsList
plugins={pluginsData.plugins}
categories={pluginsData.categories}
capabilities={pluginsData.capabilities}
/>

Data Shape: pluginsAPI.list() returns an object containing three arrays: plugins (type UserPlugin[]), categories (type PluginCategory[]), and capabilities (type string[]).

PluginsList

File: src/components/plugins/PluginsList.tsx

The top-level client component that orchestrates search, filtering, and display of all plugins.

Props:

PropTypeDescription
pluginsUserPlugin[]Full list of available plugins
categoriesPluginCategory[]Available category metadata
capabilitiesstring[]Available capability identifiers

State Management:

StateDefaultDescription
selectedCategorynullActive category filter
showEnabledOnlyfalseToggle to show only enabled plugins
searchQuery''Free-text search filter

Sorting: Plugins are sorted once on initial render using a stable sort: enabled plugins first, then installed plugins, then alphabetical by name. The sort order is captured in a useRef to prevent card positions from jumping when a plugin is toggled.

Search: The matchesSearch function builds a haystack from the plugin's name, description, category label, and capability labels, then checks if the lowercase query appears anywhere in the combined string.

Display Modes: When no search query or category filter is active, plugins render in a grouped-by-category layout. When searching or filtering, a flat grid is used instead.

PluginGrid

File: src/components/plugins/PluginGrid.tsx

Renders the plugin cards in either grouped or flat layout.

Props:

PropTypeDescription
pluginsUserPlugin[]Filtered plugin list
groupedbooleanWhether to group by category
searchQuerystringCurrent search term (for empty state)
onClearSearch() => voidClears the search filter

Empty State: When no plugins match, shows a contextual message: either a search-specific empty state with a clear button, or a generic empty message.

Grid Layout: Uses a responsive CSS grid: grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4.

PluginCard

File: src/components/plugins/PluginCard.tsx

Each card displays a single plugin with its metadata, toggle controls, and navigation links.

Card Sections:

SectionContent
Header RowPlugin icon (40px), name, version, system/built-in badges, enable/disable button
DescriptionTwo-line truncated description (line-clamp-2)
Capability BadgesCategory badge + up to 2 extra capability badges, with overflow count
FooterSettings link (to detail page) and documentation link (external)

Visual Indicators: Enabled and system plugins get a ring-2 ring-primary/20 border highlight. The enable/disable button uses usePluginToggle for optimistic UI updates.

Hidden Capabilities: The HIDDEN_CAPABILITIES set filters out internal capabilities that should not appear as badges on the card.

Plugin Detail Page

Route: /plugins/[pluginId] File: src/app/[locale]/(dashboard)/plugins/[pluginId]/page.tsx

The server component fetches the plugin data and conditionally checks for an OAuth connection.

const plugin = await pluginsAPI.get(pluginId);

let oauthConnection: OAuthConnectionInfo | null | undefined;
if (plugin.capabilities.includes('oauth')) {
oauthConnection = await oauthAPI.checkConnection(plugin.pluginId);
}

If the plugin fetch throws an error, the page calls notFound() to render a 404.

PluginSettings

File: src/components/plugins/PluginSettings.tsx

The main detail page component, divided into four sections.

Sections:

SectionConditionDescription
Plugin HeaderAlwaysIcon, name, version, badges, enable/disable button, author, homepage link
OAuth ConnectionPlugin has oauth capabilityRenders PluginOAuthConnection widget
Settings FormPlugin has configurable settingsForm fields with save button
ReadmePlugin has readme contentRendered markdown documentation

Hook Integration:

The component uses two hooks to manage its interactive state:

  • usePluginSettings -- Manages the settings form with scopes ['global', 'user'], validation, secret splitting, and the save lifecycle. The onSave callback calls updatePluginSettings server action.
  • usePluginToggle -- Manages enable/disable with optimistic UI, confirmation dialogs, and auto-enable-for-works option.

Enable/Disable Toggle: System plugins cannot be toggled (the button is hidden). Non-system plugins show either a "Disable" button (danger-styled) or an "Enable" button (primary-styled).

Settings Form: Uses PluginSettingsFormFields to render fields based on visibleProperties from the hook. The save button is disabled when no changes exist or when saving is in progress. A success checkmark appears for 3 seconds after saving.

PluginEnablePanel

File: src/components/plugins/PluginEnablePanel.tsx

A dialog shown when enabling a plugin that supports work scope.

Props:

PropTypeDescription
openbooleanControls dialog visibility
autoEnableForDirsbooleanCheckbox state for auto-enable
onAutoEnableChange(checked: boolean) => voidCheckbox handler
onCancel() => voidCancel handler
onConfirm() => voidConfirm handler
isPendingbooleanLoading state

The panel includes a checkbox that lets users automatically enable the plugin for all their existing works.

PluginDisableWarning

File: src/components/plugins/PluginDisableWarning.tsx

A confirmation dialog shown before disabling a plugin. Displays a warning message about the cascade effect (disabling at user level also disables for all works). Uses AlertTriangle icon and danger-styled confirmation button.

Server Actions Used

ActionSourceDescription
enablePluginsrc/app/actions/plugins.tsEnables a plugin for the user
disablePluginsrc/app/actions/plugins.tsDisables a plugin for the user
updatePluginSettingssrc/app/actions/plugins.tsUpdates plugin settings (regular + secret)
enableWorkPluginsrc/app/actions/plugins.tsEnables a plugin for a specific work
disableWorkPluginsrc/app/actions/plugins.tsDisables a plugin for a specific work
fetchModelssrc/app/actions/plugins.tsFetches AI models for a plugin
setActiveCapabilitysrc/app/actions/plugins.tsSets active capability for multi-capability plugins

Additional Components

ComponentFileDescription
PluginSearchBarPluginSearchBar.tsxSearch input for filtering the plugin list
PluginCategoryFilterPluginCategoryFilter.tsxCategory pills and enabled-only toggle
PluginIconPluginIcon.tsxRenders plugin icons (SVG, URL, or emoji fallback)
PluginReadmePluginReadme.tsxRenders markdown readme content
PluginSettingsFormFieldsPluginSettingsFormFields.tsxIterates over visible properties and renders form fields
PluginSettingsFieldform/PluginSettingsField.tsxIndividual field renderer (text, password, select, etc.)
PluginSettingsArrayFieldform/PluginSettingsArrayField.tsxArray-type settings field
PluginSettingsObjectFieldform/PluginSettingsObjectField.tsxObject-type settings field
PluginModelSelectform/PluginModelSelect.tsxAI model selection dropdown with lazy loading