Extension authoring
OpenCode Superapp uses a model familiar to VS Code Extension authors: one
installable package, an optional main entrypoint, activation events,
contribution points, a public API, and a separate Extension Host. The formats
and APIs are adapted to Superapp and are not compatible with VS Code
Extensions or .vsix packages.
There is no released CLI or marketplace yet. Create a folder and install it from Extensions > Install > Install from folder.
Minimum package
Section titled “Minimum package”my-extension/├── package.json└── icon.svg{ "$schema": "https://opencode-superapp.local/schemas/extension-package.schema.json", "name": "my-extension", "publisher": "example.author", "version": "1.0.0", "displayName": "My Extension", "description": "Explain what this Extension adds.", "engines": { "opencodeSuperapp": ">=0.1.0" }, "icon": "icon.svg", "contributes": {}}Superapp derives the ID as example.author.my-extension. Do not add source,
trust, installed state, enabled state, permissions, requirements, or private
service IDs to the manifest. Package paths must be relative and stay inside the
folder; symlinks are rejected.
Optional fields include main, activationEvents, readme, license,
repository, author, homepage, keywords, os, and cpu. Superapp does
not install npm dependencies or execute package lifecycle scripts.
Skill-only Extension
Section titled “Skill-only Extension”{ "contributes": { "skills": [{ "path": "skills/review/SKILL.md" }] }}The file must be a complete Skill with name and description frontmatter.
Use Skills > Import Skill instead when one loose Skill does not need package
identity, versioning, or distribution.
MCP-only Extension
Section titled “MCP-only Extension”A remote MCP server uses HTTPS:
{ "contributes": { "mcpServers": [{ "id": "example-tools", "name": "Example Tools", "description": "Tools from the Example service.", "runtime": { "kind": "remote", "url": "https://mcp.example.com" } }] }}Package-local MCP implementations use either:
{ "kind": "javascript", "entrypoint": "mcp/index.mjs" }or a package-relative executable command:
{ "kind": "executable", "command": ["bin/example-server", "--stdio"] }OpenCode owns connection, authentication, tools, and live status. Local packages cannot request Superapp’s private integrated services.
Command Extension
Section titled “Command Extension”Declare the command and executable entrypoint:
{ "main": "src/extension.ts", "activationEvents": ["onCommand:example.author.my-extension.hello"], "contributes": { "commands": [{ "command": "example.author.my-extension.hello", "title": "Say Hello" }] }}Register the declared ID from code:
import { commands } from "@opencode-superapp/api";import type { ExtensionContext } from "@opencode-superapp/api";
export function activate(context: ExtensionContext) { context.subscriptions.push( commands.registerCommand("example.author.my-extension.hello", () => "Hello") );}activate(context) is required when main is present; deactivate() is
optional. The host validates runtime registrations against package.json.
Composer action
Section titled “Composer action”A composer action places an already declared command:
{ "contributes": { "commands": [{ "command": "example.author.my-extension.rewrite", "title": "Rewrite selection" }], "menus": { "composer": [{ "command": "example.author.my-extension.rewrite" }] } }}The API offers limited composer.insertText() and
composer.replaceSelection() helpers. It does not expose unrestricted draft
or application state.
Right-panel app
Section titled “Right-panel app”Declare a webview and activate when it opens:
{ "main": "src/extension.mjs", "activationEvents": ["onView:notes"], "contributes": { "views": { "rightPanel": [{ "id": "notes", "name": "Notes", "type": "webview" }] } }}Register a provider:
import { window } from "@opencode-superapp/api";
export function activate(context) { context.subscriptions.push(window.registerWebviewViewProvider("notes", { resolveWebviewView(view) { view.webview.options = { enableScripts: true }; view.webview.html = `<!doctype html><textarea class="scratchpad"></textarea>`; view.webview.onDidReceiveMessage(message => context.log.info("View message", { message })); } }));}Webviews run in sandboxed iframes without direct Tauri or parent-DOM access.
Use acquireSuperappApi().postMessage(value) inside the webview and
view.webview.onDidReceiveMessage() in Extension code. Use
view.webview.postMessage() for the reverse direction. Package assets must be
converted with asWebviewUri(). Persistent view state is not supported in v1.
Activation and trust
Section titled “Activation and trust”Supported activation events are onCommand:<id>, onView:<id>,
onStartupFinished, and *. Command and view events are inferred when omitted.
Events for unsupported APIs, including onLanguage, are rejected.
Imported executable code runs with your operating-system user privileges in a separate crash-isolation process. It has no public access to private native services or arbitrary Tauri commands. Users see it as Unverified local extension.
Update and .ocx
Section titled “Update and .ocx”The installed folder is a snapshot. Increase the semantic version before
installing an update; the same or an older version is rejected. An .ocx file
is simply a ZIP of the folder with package.json at its root, or within exactly
one top-level directory. Do not include node_modules, caches, secrets, or
unrelated project files.