Guide for creating and implementing plugins to extend OpenCode functionality, including hooks, custom tools, and event handling
Plugins allow you to extend OpenCode by hooking into various events, adding custom tools, and customizing behavior. This skill guides you through creating plugins using JavaScript/TypeScript modules that export plugin functions.
A plugin is a JavaScript/TypeScript module that exports one or more plugin functions. Each function receives a context object and returns a hooks object.
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
console.log("Plugin initialized!")
return {
// Hook implementations go here
}
}
project: Current project informationdirectory: Current working directoryworktree: Git worktree pathclient: OpenCode SDK client for AI interactions$: Bun's shell API for executing commandsimport type { Plugin } from "@opencode-ai/plugin"
export const MyPlugin: Plugin = async ({ project, client, $, directory, worktree }) => {
return {
// Type-safe hook implementations
}
}
Plugins are loaded from:
.sgai/plugin directory in your project~/.config/opencode/pluginListen for OpenCode events:
return {
event: async ({ event }) => {
if (event.type === "session.idle") {
await $`osascript -e 'display notification "Session completed!" with title "opencode"'`
}
}
}
Add custom tools:
import { type Plugin, tool } from "@opencode-ai/plugin"
export const CustomToolsPlugin: Plugin = async (ctx) => {
return {
tool: {
mytool: tool({
description: "This is a custom tool",
args: {
foo: tool.schema.string(),
},
async execute(args, ctx) {
return `Hello ${args.foo}!`
},
}),
},
}
}
Intercept tool execution:
return {
"tool.execute.before": async (input, output) => {
if (input.tool === "read" && output.args.filePath.includes(".env")) {
throw new Error("Do not read .env files")
}
}
}
.sgai/plugin/Send notifications on events:
export const NotificationPlugin = async ({ $ }) => {
return {
event: async ({ event }) => {
if (event.type === "session.idle") {
await $`notify-send "OpenCode" "Session completed"`
}
}
}
}
Prevent access to sensitive files:
export const FileProtection = async () => {
return {
"tool.execute.before": async (input) => {
const protectedPatterns = [/\.env$/, /secrets\//, /private\//]
if (input.tool === "read" && protectedPatterns.some(p => p.test(input.args.filePath))) {
throw new Error("Access denied to protected file")
}
}
}
}
Add domain-specific tools:
export const DomainTools: Plugin = async () => {
return {
tool: {
analyzeCode: tool({
description: "Analyze code for specific patterns",
args: {
filePath: tool.schema.string(),
pattern: tool.schema.string()
},
execute: async (args) => {
// Implementation
return "Analysis complete"
}
})
}
}
}