---
title: Plugin UI Contributions
---

# Plugin UI Contributions

## What it is

**Status:** shipped (always on, landed 2026-08-06). A plugin can add its own buttons to the header
toolbar and the session right-click menu, and navigate the app to a session, hub, or view — there is
no Lab flag for it and nothing to enable.

A plugin that opts in gets a **read-only presence in the Omniscio shell**. Through its webview pane
it can do two things, both mediated by the host:

1. **Navigate the app** — `navigation.goTo({ kind, id? })` switches the active view to a core session, project, inbox, or virtual project — the same chokepoints the app itself uses when you click around. It is reversible (it never destroys anything), so it is the most benign primitive.
2. **Contribute chrome** — `toolbar.setItems([...])` and `contextMenu.setSessionItems([...])` set the buttons the plugin wants in the header toolbar and in the right-click menu of any session. Both are **replace-semantics per plugin**: whatever the plugin last sent is what shows, and sending an empty list removes its items.

The contributed buttons and menu items are read-only chrome until a click lands back in the plugin's own pane — clicking one sends a `command:<id>` event to *that* plugin's webview and nothing else. Anything that actually changes data still goes through the plugin's normal approval flow.

## Where to find it

There is nothing to switch on. Look at the app's own chrome: a contributing plugin's buttons appear
on the **header toolbar**, namespaced to that plugin, and its items appear in the **right-click menu
of any session**. The plugin's own pane is where the result of a click shows up, and it opens from
wherever that plugin sits in the app.

A plugin only gets here if you consented to the `navigation` and/or `chrome` permission when you
installed it, from the Plugin Marketplace.

## How it behaves

1. Nothing to enable — the feature ships always-on (it landed with the plugin-platform parity slice on 2026-08-06).
2. A plugin opts in by declaring `navigation` and/or `chrome` in its manifest `permissions` list. The marketplace asks you to consent when a plugin requests a permission; a plugin that doesn't declare it can't use it.
3. Once installed, a contributing plugin's buttons appear (namespaced `plugin:<pluginId>:<id>`) on the header toolbar, and its items appear in the right-click menu of any session.
4. Click one — a `command:<id>` event (plus the session id, for menu items) is delivered to that plugin's own webview. Open its pane to see the result.
5. Desktop only: the paired phone has no plugin webview surface, so plugin navigation and chrome clicks are refused over the phone/WS bridge (see the implementation facts below).

## For agents

- **Manifest opt-in:** `permissions: ['navigation']` (navigate), `['chrome']` (toolbar + session-menu items), or both — declared once in the manifest; consent derives from the manifest label + description. There is no separate `ui.contributions` manifest field.
- **One item shape, no drift:** toolbar and context-menu items validate against the same `pluginChromeItemSchema` the outbound push uses. An item has `id` (kebab-case), `label` (text only), and an `icon` that is a **NAME** from an allow-list — never a URL or inline SVG (unknown name → fallback icon). Arrays are capped: 20 toolbar items, 10 menu items.
- **Guard order in each bridge handler:** `requirePluginUiAccess()` first (the shipped feature gate — always true now, kept as a fail-closed tripwire), then the per-permission gate (`hasNavigationPermission` / `hasChromePermission`), then the wire schema, then (navigation only) the per-plugin rate limit, then emit the validated push.
- **Navigation:** rate-limited per plugin over a window bucket (a runaway plugin can't focus-steal in a loop); a `kind: 'virtual'` target id must match `/^__[a-z0-9_]+__$/`.
- **Click round-trip:** the renderer invokes `IPC.PLUGIN_DISPATCH_COMMAND` (`plugin:dispatch-command`) with `{ pluginId, command, sessionId? }`; Main relays a `command:<id>` event to ONLY that plugin's webview. Navigation emits the `IPC.PLUGIN_NAVIGATE_VIEW` (`plugin:navigate-view`) push to the active renderer.
- **Desktop-only (`plugin-chrome-is-desktop-only`):** both channels sit in `BLOCKED_CHANNELS` in [web-access-ws-channels.ts](../../src/main/services/web/web-access-ws-channels.ts) — the phone/WS bridge refuses them without even calling the handler.
- **Files:** bridge handlers [navigation-handler.ts](../../src/main/ipc/plugin-bridge/navigation-handler.ts), [toolbar-handler.ts](../../src/main/ipc/plugin-bridge/toolbar-handler.ts), [context-menu-handler.ts](../../src/main/ipc/plugin-bridge/context-menu-handler.ts) + the gate [permissions.ts](../../src/main/ipc/plugin-bridge/permissions.ts); wire schemas [bridge-method-schemas.ts](../../src/main/ipc/bridge-method-schemas.ts); channel names [ipc-channels/plugins.ts](../../src/shared/ipc-channels/plugins.ts); renderer chrome store [plugin-chrome-store.ts](../../src/renderer/src/stores/plugin-chrome-store.ts) with renderers [plugin-toolbar-items.ts](../../src/renderer/src/features/toolbar/plugin-toolbar-items.ts) + [useSessionContextMenuActions.ts](../../src/renderer/src/features/dashboard/useSessionContextMenuActions.ts).
- **Contract + invariants:** [plugin-ui-contributions-contract.md](../../.claude/memory/contracts/plugin-ui-contributions-contract.md), locked by tests in [plugin-chrome-bridge.test.ts](../../tests/unit/ipc/plugin-chrome-bridge.test.ts).

## Related

[Plugin Marketplace](plugin-marketplace.md) covers installing plugins and the permission-consent UX
this page's opt-in rides on. For the higher-power capabilities a plugin can call, see
[Plugin Bridge Capabilities](plugin-bridge-capabilities.md); for the in-plugin session experience —
sessions started from inside a plugin's own surface — see
[Plugin AI-Native Sessions](plugin-ai-native-sessions.md). The read-only disclosure that a plugin's
AI can take actions is [Plugin Capability Card](plugin-capability-card.md), and how a session
discovers and drives installed plugins is [Plugin CLI Discovery](plugin-cli-discovery.md).
