UI contributions

Sidebar tabs and cards, settings pages, topper items, commands, and web views.

UI contributions live on definePlugin({ ui: ... }). Every contribution accepts an optional when?: (ctx) => boolean render guard, evaluated by the host against your config.

import { definePlugin, defineSidebarTab } from "@pragma-sh/plugin";

export default definePlugin({
  name: "Review Queue",
  ui: {
    sidebarTabs: [
      defineSidebarTab({
        id: "queue",
        title: "Queue",
        icon: MyIcon, // optional PluginIcon
        component: Queue, // receives { webViewPayload? }
        when: (ctx) => ctx.config.enabled,
      }),
    ],
  },
});

Tabs render in the project sidebar and receive the project context through the hooks.

sidebarCards: [defineSidebarCard({ title: "Alerts", component: AlertsCard })];

Collapsible cards below the worktree tree, next to the built-in Ports and Scratchpads cards.

Settings pages

settingsPages: [
  defineSettingsPage({ id: "queue", title: "Review Queue", component: QueueSettings }),
];

Adds a page to Settings, available in both global and project scope like the built-ins. Pages are not their own navigation section: they appear nested under their plugin in the Plugins list, and open from there.

Topper items

topper: [defineTopperItem({ align: "right", component: SyncBadge })];

Small components in the top toolbar's left or right cluster (next to the agents menu and usage-limits gauge).

Commands

import { definePlugin, defineCommand } from "@pragma-sh/plugin";

commands: [
  defineCommand({
    id: "review-queue.open",
    title: "Open review queue",
    defaultBinding: "mod+shift+r",
    run: (ctx, args) => ctx.notify("Review queue opened"),
  }),
];

Commands appear in the command palette (command mode) and are bindable like built-ins; defaultBinding merges with the user's keybinding files. hidden keeps a command out of the palette while keeping it callable.

Web views

import { definePlugin, defineWebView } from "@pragma-sh/plugin";

const webView = defineWebView({ id: "docs", title: "Docs", component: DocsPage });

// open it from a command or hook:
webView.open({ title: "Project docs", payload: { page: "intro" }, dedupeKey: "docs" });

openWebView(webViewOrId, options) opens by handle or unambiguous id. payload reaches the component via useWebViewPayload(), and dedupeKey focuses an existing instance instead of opening a second one. Web views render as their own tab kind.

Config

import { z } from "@pragma-sh/plugin"; // the host's zod — never bundle your own

config: z.object({ enabled: z.boolean().default(true), level: z.number().optional() }),

The schema validates every plugins[].config entry at load; usePluginConfig() and ctx.config hand your components the parsed value.

On this page