11 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	OpenCode Plugin Development Reference
Overview
OpenCode plugins are JavaScript/TypeScript modules that extend OpenCode's functionality by hooking into various events and customizing behavior. Plugins can add custom tools, modify LLM parameters, handle authentication, intercept tool execution, and respond to system events.
Plugin Locations
Plugins are automatically loaded from:
- Project-local: 
.opencode/plugin/directory in your project - Global: 
~/.config/opencode/plugin/directory 
Note: Local plugin files are auto-discovered and do NOT need to be listed in opencode.jsonc's plugin array. The plugin array is only for npm package plugins.
Basic Plugin Structure
File Format
Plugins are .js or .ts files that export one or more plugin functions:
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  // Initialization code here
  console.log("Plugin initialized");
  
  return {
    // Hook implementations
  };
};
Plugin Context (Input Parameters)
Every plugin function receives a context object with:
| Parameter | Type | Description | 
|---|---|---|
project | 
Project | 
Current project information | 
directory | 
string | 
Current working directory | 
worktree | 
string | 
Git worktree path | 
client | 
OpencodeClient | 
OpenCode SDK client for API access | 
$ | 
BunShell | 
Bun's shell API for executing commands | 
Plugin Return Value (Hooks)
Plugins return an object containing hook implementations. All hooks are optional.
Available Hooks
1. event Hook
Respond to OpenCode system events.
event: async ({ event }) => {
  if (event.type === "session.idle") {
    // OpenCode is waiting for user input
  }
}
Common Event Types:
session.idle- Session is waiting for user inputsession.start- Session has startedsession.end- Session has ended- Additional events available via SDK
 
2. config Hook
React to configuration changes.
config: async (config) => {
  console.log("Config:", config.model, config.theme);
}
3. tool Hook
Add custom tools that the LLM can call.
import { tool } from "@opencode-ai/plugin";
return {
  tool: {
    mytool: tool({
      description: "Description shown to LLM",
      args: {
        query: tool.schema.string().describe("Query parameter"),
        count: tool.schema.number().optional().describe("Optional count")
      },
      async execute(args, context) {
        // context contains: { agent, sessionID, messageID }
        return `Result: ${args.query}`;
      }
    })
  }
};
Tool Schema Types (using Zod):
tool.schema.string()tool.schema.number()tool.schema.boolean()tool.schema.object({ ... })tool.schema.array(...).optional()- Make parameter optional.describe("...")- Add description for LLM
4. auth Hook
Provide custom authentication methods.
auth: {
  provider: "my-service",
  methods: [
    {
      type: "api",
      label: "API Key"
    },
    {
      type: "oauth",
      label: "OAuth Login",
      authorize: async () => ({
        url: "https://...",
        instructions: "Login instructions",
        method: "auto",
        callback: async () => ({ 
          type: "success", 
          key: "token" 
        })
      })
    }
  ]
}
5. chat.message Hook
Called when a new user message is received.
"chat.message": async ({}, output) => {
  console.log("Message:", output.message.text);
  console.log("Parts:", output.parts);
}
6. chat.params Hook
Modify parameters sent to the LLM.
"chat.params": async (input, output) => {
  // input: { model, provider, message }
  output.temperature = 0.7;
  output.topP = 0.95;
  output.options = { /* custom options */ };
}
7. permission.ask Hook
Intercept permission requests.
"permission.ask": async (permission, output) => {
  if (permission.tool === "bash" && permission.args.command.includes("rm")) {
    output.status = "deny"; // or "allow" or "ask"
  }
}
8. tool.execute.before Hook
Intercept tool execution before it runs.
"tool.execute.before": async (input, output) => {
  // input: { tool, sessionID, callID }
  // output: { args }
  
  if (input.tool === "read" && output.args.filePath.includes(".env")) {
    throw new Error("Cannot read .env files");
  }
}
9. tool.execute.after Hook
Modify tool execution results.
"tool.execute.after": async (input, output) => {
  // input: { tool, sessionID, callID }
  // output: { title, output, metadata }
  
  console.log(`Tool ${input.tool} returned:`, output.output);
}
Using the OpenCode SDK Client
The client parameter provides full API access:
Common Operations
// Get current project
const project = await client.project.current();
// List sessions
const sessions = await client.session.list();
// Read a file
const content = await client.file.read({ 
  query: { path: "src/index.ts" } 
});
// Search for text
const matches = await client.find.text({ 
  query: { pattern: "TODO" } 
});
// Show toast notification
await client.tui.showToast({ 
  body: { message: "Task complete", variant: "success" } 
});
// Send a prompt
await client.session.prompt({
  path: { id: sessionID },
  body: {
    model: { providerID: "anthropic", modelID: "claude-3-5-sonnet-20241022" },
    parts: [{ type: "text", text: "Hello!" }]
  }
});
Available SDK Methods
App: app.log(), app.agents()
Project: project.list(), project.current()
Sessions: session.list(), session.get(), session.create(), session.delete(), etc.
Files: file.read(), file.status(), find.text(), find.files(), find.symbols()
TUI: tui.appendPrompt(), tui.showToast(), tui.openHelp(), etc.
Config: config.get(), config.providers()
Events: event.subscribe() (for event streaming)
See full SDK documentation: https://opencode.ai/docs/sdk/
Using Bun Shell ($)
Execute shell commands easily:
// Simple command
await $`notify-send "Hello"`;
// Get output
const text = await $`git status`.text();
const json = await $`hyprctl clients -j`.json();
// Command with variables
const file = "test.txt";
await $`cat ${file}`;
// Array of arguments
const args = ["notify-send", "-u", "normal", "Title", "Body"];
await $`${args}`;
Complete Example: Notification Plugin
export const NotificationPlugin = async ({ project, client, $, directory, worktree }) => {
  console.log("Notification plugin initialized");
  
  return {
    // Send notification when OpenCode is idle
    event: async ({ event }) => {
      if (event.type === "session.idle") {
        const pid = process.pid;
        const iconPath = `${process.env.HOME}/.config/opencode/icon.png`;
        
        try {
          // Get window info from Hyprland
          const clientsJson = await $`hyprctl clients -j`.text();
          const clients = JSON.parse(clientsJson);
          const window = clients.find(c => c.pid === pid);
          
          // Send notification with action
          const result = await $`notify-send -a "OpenCode" -u normal -i ${iconPath} -A focus=Focus "OpenCode Ready" "Waiting for input in ${directory}"`.text();
          
          // Handle action click
          if (result.trim() === "focus" && window?.address) {
            await $`hyprctl dispatch focuswindow address:${window.address}`;
          }
        } catch (error) {
          console.error("Notification error:", error);
        }
      }
    },
    
    // Add custom tool
    tool: {
      notify: tool({
        description: "Send a system notification",
        args: {
          message: tool.schema.string().describe("Notification message"),
          urgency: tool.schema.enum(["low", "normal", "critical"]).optional()
        },
        async execute(args) {
          await $`notify-send -u ${args.urgency || "normal"} "OpenCode" ${args.message}`;
          return "Notification sent";
        }
      })
    },
    
    // Modify LLM parameters
    "chat.params": async (input, output) => {
      // Lower temperature for code-focused tasks
      if (input.message.text?.includes("refactor") || input.message.text?.includes("bug")) {
        output.temperature = 0.3;
      }
    },
    
    // Prevent dangerous operations
    "tool.execute.before": async (input, output) => {
      if (input.tool === "bash" && output.args.command.includes("rm -rf")) {
        throw new Error("Dangerous command blocked by plugin");
      }
    }
  };
};
TypeScript Support
For type-safe plugins, import types from the plugin package:
import type { Plugin } from "@opencode-ai/plugin";
export const MyPlugin: Plugin = async (ctx) => {
  return {
    // Type-safe hook implementations
  };
};
Best Practices
- Error Handling: Always wrap risky operations in try-catch blocks
 - Async Operations: All hook functions should be async
 - Console Logging: Use 
console.log()for debugging - visible in OpenCode logs - Resource Cleanup: Clean up resources when plugins are reloaded
 - Minimal Processing: Keep hooks fast to avoid blocking OpenCode
 - Security: Validate inputs, especially in custom tools
 - Documentation: Add clear descriptions to custom tools for the LLM
 
Common Use Cases
System Integration
- Send desktop notifications (Linux, macOS, Windows)
 - Integrate with window managers (Hyprland, i3, etc.)
 - System clipboard operations
 - File system watching
 
Development Workflow
- Run tests on code changes
 - Format code automatically
 - Update documentation
 - Git operations
 
LLM Enhancement
- Add domain-specific tools
 - Custom prompt preprocessing
 - Response filtering
 - Context augmentation
 
Security & Compliance
- Block dangerous commands
 - Prevent access to sensitive files
 - Audit tool usage
 - Rate limiting
 
Debugging
- 
View Logs: Run OpenCode with debug output
G_MESSAGES_DEBUG=all opencode - 
Console Logging: Use
console.log(),console.error()in plugins - 
Test Independently: Test shell commands and SDK calls outside plugins first
 - 
Hot Reload: Plugins are reloaded when files change (in development mode)
 
Related Documentation
Template Plugin
/**
 * Template Plugin
 * Description: What this plugin does
 */
export const TemplatePlugin = async ({ project, client, $, directory, worktree }) => {
  // Initialization
  console.log("Template plugin initialized");
  
  // You can store state here
  let pluginState = {};
  
  return {
    // Implement the hooks you need
    event: async ({ event }) => {
      // Handle events
    },
    
    tool: {
      // Add custom tools
    },
    
    "chat.params": async (input, output) => {
      // Modify LLM parameters
    },
    
    // ... other hooks as needed
  };
};