Files
claudecodeui/examples/plugins/hello-world

Hello World Plugin

A minimal example showing how to build a plugin for Claude Code UI.

How plugins work

A plugin's UI runs client-side inside a sandboxed iframe. The backend handles plugin lifecycle (install, update, uninstall) and serves the plugin's files as static assets.

┌─────────────────────────────────────────────────┐
│  Backend (server)                               │
│                                                 │
│  Lifecycle (spawns child processes):            │
│    git clone / git pull    Install & update     │
│    npm install             Dependency setup     │
│                                                 │
│  Runtime:                                       │
│    GET  /api/plugins              List plugins  │
│    GET  /api/plugins/:name/assets/* Serve files │
│    PUT  /api/plugins/:name/enable Toggle on/off │
│    DELETE /api/plugins/:name      Uninstall     │
└──────────────────────┬──────────────────────────┘
                       │ serves static files
┌──────────────────────▼──────────────────────────┐
│  Frontend (browser)                             │
│                                                 │
│  Plugin iframe ◄──postMessage──► Host app       │
│  (sandboxed)        ccui:context                │
│                     ccui:request-context         │
└─────────────────────────────────────────────────┘

Plugin structure

A plugin is a directory with at minimum two files:

my-plugin/
  manifest.json   # Required — plugin metadata
  index.html      # Entry point (referenced by manifest.entry)
  styles.css      # Optional — any static assets alongside entry
  app.js          # Optional — JS loaded by your HTML

All files in the plugin directory are accessible via /api/plugins/:name/assets/. Use relative paths in your HTML to reference them (e.g., <link href="styles.css">, <script src="app.js">).

manifest.json

{
  "name": "hello-world",        // Unique id — alphanumeric, hyphens, underscores only
  "displayName": "Hello World", // Shown in the UI
  "version": "1.0.0",
  "description": "Short description shown in settings.",
  "author": "Your Name",
  "icon": "Puzzle",             // Lucide icon name (see available icons below)
  "type": "iframe",             // Rendering method — only "iframe" is supported today
  "slot": "tab",                // Where the plugin appears — only "tab" is supported today
  "entry": "index.html",        // Path to the entry file, relative to the plugin directory
  "permissions": []              // Reserved for future use
}

Available icons

Puzzle (default), Box, Database, Globe, Terminal, Wrench, Zap, BarChart3, Folder, MessageSquare, GitBranch

Installation

Manual: Copy your plugin folder into ~/.claude-code-ui/plugins/.

From git: In Settings > Plugins, paste a git URL and click Install. The repo is cloned into the plugins directory.

Backend details

The backend manages the full plugin lifecycle. During install and update, it spawns child processes on the server:

  • Install — runs git clone --depth 1 to clone the repo, validates manifest.json, then runs npm install --production --ignore-scripts if a package.json exists. Install scripts are blocked (--ignore-scripts) to prevent arbitrary code execution.
  • Update — runs git pull --ff-only in the plugin directory, re-validates the manifest, re-runs npm install --production --ignore-scripts if needed.
  • Uninstall — deletes the plugin directory recursively from disk.

At runtime (after install), the backend:

  • Discovery — scans ~/.claude-code-ui/plugins/ for directories containing a valid manifest.json
  • Asset serving — serves any file inside a plugin directory at /api/plugins/:name/assets/* with correct MIME types. Path traversal outside the plugin directory is blocked.
  • Config — per-plugin enabled/disabled state stored in ~/.claude-code-ui/plugins.json

Plugins cannot register custom server routes, middleware, or execute their own backend code. The server only serves their files statically. If your plugin needs external data, fetch it from the iframe directly (subject to CORS).

Frontend — Context API

The host app sends a ccui:context message to the iframe whenever the theme, project, or session changes:

window.addEventListener('message', (event) => {
  if (event.data?.type !== 'ccui:context') return;

  const { theme, project, session } = event.data;
  // theme   — "dark" | "light"
  // project — { name, path } | null
  // session — { id, title } | null
});

To request the current context on load:

window.parent.postMessage({ type: 'ccui:request-context' }, '*');

Frontend — Mobile

On desktop, each enabled plugin gets its own tab in the tab bar. On mobile, plugins are grouped under a single "More" button in the bottom navigation to save space.

Security

Plugins run in a sandboxed iframe with allow-scripts allow-forms allow-popups. They cannot access the host app's cookies, localStorage, or auth tokens (allow-same-origin is intentionally omitted). Any static assets (CSS, JS, images) can be placed alongside the entry file and referenced with relative paths.

npm postinstall scripts are blocked during installation (--ignore-scripts), so plugins should ship pre-built assets.

Try it

cp -r examples/plugins/hello-world ~/.claude-code-ui/plugins/

Then open Settings > Plugins — "Hello World" should appear. Enable it and its tab will show up.