Skip to content

[Feature]: Plugin UI Extension System — Allow Plugins to Contribute Native Pages to Control UI #66944

Description

@iamalin

Summary

Allow plugins to declare UI pages in openclaw.plugin.json that appear as native tabs inside the Control UI. Plugin pages are built as Lit Web Components (same tech stack as the main UI), dynamically discovered and loaded at runtime, sharing the same theme, Lit runtime, and gateway client. Users cannot distinguish plugin pages from built-in views.

Problem to solve

The Control UI is currently a closed SPA — all navigation tabs and views are hardcoded at build time. Plugins can serve standalone pages via registerHttpRoute() (e.g., the diffs plugin at /plugins/diffs/view/), but cannot contribute pages that render inside the main UI shell.

This forces plugin UI into one of two bad options:

  • Standalone page: separate browser tab, no shared theme/state, jarring navigation
  • Hardcoded into core: tight coupling, not distributable as a plugin

Concrete use case: the Skill Development Studio plugin needs a management page (skill list, recording controls) that should appear as a tab under the "agent" group — indistinguishable from built-in views like "agents" or "nodes".

Proposed solution

High-Level Flow

Plugin Manifest (ui.pages)
    → Server collects metadata during plugin loading
    → Bootstrap config delivers pluginPages[] + import map to browser
    → Control UI injects import map, dynamic-imports plugin JS
    → Plugin registers Custom Element
    → Main UI renders it as a native tab

Architecture Diagram

                  Build Time                                     Runtime

 ┌─ Plugin ─────────────────────┐      ┌─ Gateway ──────────────────────────────────────┐
 │                              │      │                                                │
 │  ui/                         │      │  Plugin Loader                                 │
 │    skill-dev-page.ts (Lit)   │      │    reads manifest.ui.pages                     │
 │    vite.config.ts            │      │    populates PluginRecord.uiPages              │
 │      → build                 │      │                     │                          │
 │        → dist/ui/page.js     │      │  Bootstrap Config   ▼                          │
 │                              │      │    /__openclaw/control-ui-config.json           │
 │  openclaw.plugin.json        │      │    + pluginPages: [...]                        │
 │    ui.pages[...]             │      │    + pluginImportMap: { imports: {...} }        │
 │                              │      │                                                │
 │  index.ts (server)           │      │  HTTP Static Assets                            │
 │    registerHttpRoute(...)  ──┼──────┼──  /plugins/skill-dev/ui/page.js               │
 │    serves dist/ui/*          │      │                                                │
 └──────────────────────────────┘      └──────────────────────┬─────────────────────────┘
                                                              │
                                       ┌─ Control UI (Browser)┴─────────────────────────┐
                                       │                                                 │
                                       │  1. fetch bootstrap config                      │
                                       │  2. read pluginPages[]                          │
                                       │  3. inject import map (lit → main UI's chunk)   │
                                       │  4. dynamic import(entryUrl) per page           │
                                       │  5. Custom Element registered                   │
                                       │  6. rendered as native tab in sidebar           │
                                       └─────────────────────────────────────────────────┘

Why Unified Tech Stack (Lit), Not iframe

Concern iframe Lit Web Components (proposed)
Visual consistency Plugin must replicate theme manually Inherits CSS variables (--bg, --accent, --border...) automatically
Theme switching Requires postMessage sync Automatic — same DOM tree
Navigation Separate page feel, URL mismatch Native tab, seamless
Communication with gateway postMessage (async, untyped) Direct gateway.request() (sync-style, typed)
Keyboard / focus / scroll iframe quirks (well-known pain) Native behavior
Runtime overhead Extra browsing context per plugin Zero — shared Lit instance via import map
Plugin dev constraint Any framework Must use Lit + CSS variables
Constraint cost Low: Lit is a thin wrapper over standard Web Components

Alternatives considered

No response

Impact

Affected Users / Systems

  • Plugin developers — anyone building OpenClaw plugins that need a management UI (skill-dev, future workflow editors, monitoring dashboards, etc.)
  • End users of plugin-powered features — must leave the Control UI to access plugin pages, or the feature must be hardcoded into core
  • Core maintainers — every plugin with a UI need currently results in a PR to the core UI codebase, increasing coupling and review burden

Severity: Blocks Workflow

This is not a cosmetic issue. Without this capability, plugin authors face a hard architectural wall:

  • No path to ship a plugin with integrated UI. Plugins that require user-facing management (config panels, visual editors, monitoring views) cannot be distributed as self-contained packages. The UI portion must either live outside the main shell (degraded UX) or be merged into core (defeats the purpose of plugins).
  • Core becomes a bottleneck. Every new UI surface requires changes to navigation.ts, app-render.ts, and the build pipeline — even when the feature is logically independent.

Frequency: Every Plugin with a UI

This is not an edge case. It affects every plugin that needs more than a config form. Known upcoming cases:

Plugin UI Need
skill-dev Skill list, recording controls, noVNC overlay
browser Browser profile management, session viewer
Future workflow/agent editors Visual node editors, execution trace viewers
Future monitoring plugins Custom dashboards, log viewers

As the plugin ecosystem grows, this gap will block an increasing number of features.

Consequences Today

  • Fragmented UX: plugin UIs live in separate browser tabs with no shared theme, no sidebar navigation, no gateway state — users context-switch constantly
  • Duplicated effort: each standalone plugin page must independently implement auth, theme, WebSocket connection, and error handling that the main UI already provides
  • Slower plugin development: plugin authors spend time on infrastructure (serving HTML, managing styles, wiring gateway) instead of feature logic
  • Reluctance to build plugins: the cost of "plugin + separate UI" is high enough that teams default to merging into core, eroding the plugin architecture

Evidence/examples

No response

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:needs-security-reviewClawSweeper marked this issue as needing security-sensitive review.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.enhancementNew feature or requestimpact:securitySecurity boundary, credential, authz, sandbox, or sensitive-data risk.issue-rating: 🌊 off-meta tidepoolIssue quality rating does not apply to this item.maturity:stableIssue affects a taxonomy feature currently scored M4/M5.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions