OpenCode has a comprehensive plugin/extension ecosystem with multiple integration mechanisms.
Plugins are JavaScript/TypeScript modules that export plugin functions receiving a context object and returning a hooks object.
| Method | Location |
|---|---|
| Local (project) | .opencode/plugins/ |
| Local (global) | ~/.config/opencode/plugins/ |
| npm | Specified in opencode.json via "plugin": ["package-name"] |
Plugins are loaded in order: global config → project config → global plugins directory → project plugins directory.
import type { Plugin } from "@opencode-ai/plugin"
export const MyPlugin: Plugin = async ({
project,
client, // OpenCode SDK client
directory, // Current working directory
worktree, // Git worktree path
serverUrl, // Server URL
$ // Bun shell API
}) => {
return {
// Hook implementations go here
}
}| Category | Hook | Description |
|---|---|---|
| Chat | chat.message |
Called when a new message is received |
chat.params |
Modify parameters sent to LLM | |
chat.headers |
Modify headers sent to LLM | |
experimental.chat.messages.transform |
Transform message history | |
experimental.chat.system.transform |
Modify system prompt | |
| Tool | tool.execute.before |
Intercept tool execution before running |
tool.execute.after |
Process tool output after execution | |
tool.definition |
Modify tool definitions sent to LLM | |
| Permission | permission.ask |
Control permission requests |
| Auth | auth |
Authentication hooks (OAuth, API key) |
| Session | event |
Subscribe to all system events |
experimental.session.compacting |
Customize context for session compaction | |
| Shell | shell.env |
Inject environment variables |
command.execute.before |
Intercept command execution |
Define tools that the LLM can call during conversations. Place them in:
.opencode/tools/(project-level)~/.config/opencode/tools/(global)
The filename becomes the tool name (e.g., database.ts → database tool).
// .opencode/tools/database.ts
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "Query the project database",
args: {
query: tool.schema.string().describe("SQL query to execute"),
},
async execute(args, context) {
return `Executed query: ${args.query}`
},
})Use named exports — they become <filename>_<exportname>:
// .opencode/tools/math.ts
export const add = tool({ /* ... */ }) // Creates tool "math_add"
export const multiply = tool({ /* ... */ }) // Creates tool "math_multiply"Tools receive context with: sessionID, messageID, agent, directory, worktree, abort signal, and metadata()/ask() methods.
OpenCode natively supports both local and remote MCP servers for external tool integration.
{
"mcp": {
"my-local-mcp": {
"type": "local",
"command": ["npx", "-y", "my-mcp-command"],
"environment": {
"MY_VAR": "value"
},
"enabled": true,
"timeout": 5000
}
}
}{
"mcp": {
"my-remote-mcp": {
"type": "remote",
"url": "https://my-mcp-server.com/mcp",
"enabled": true,
"headers": {
"Authorization": "Bearer MY_API_KEY"
}
}
}
}OpenCode automatically handles OAuth for remote MCP servers via Dynamic Client Registration (RFC 7591):
{
"oauth": {
"clientId": "{env:CLIENT_ID}",
"clientSecret": "{env:CLIENT_SECRET}",
"scope": "tools:read tools:execute"
}
}MCP tools are managed per-agent with glob patterns:
{
"tools": {
"my-mcp*": false,
"my-mcp_search": true
},
"agent": {
"my-agent": {
"tools": { "my-mcp*": true }
}
}
}Reusable instruction sets discovered on-demand via the skill tool. Placed in:
.opencode/skills/<name>/SKILL.md(project-level)~/.config/opencode/skills/<name>/SKILL.md(global)
---
name: git-release
description: Create consistent releases and changelogs
license: MIT
---
## What I do
- Draft release notes from merged PRs
- Propose a version bump
- Provide a copy-pasteable commandConfigure specialized agents in opencode.json:
{
"agent": {
"my-agent": {
"prompt": "You are a security specialist...",
"model": "anthropic/claude-sonnet-4-5",
"tools": { "bash": false }
}
}
}Plugins can provide OAuth and API-based authentication:
export const MyAuthPlugin: Plugin = async (ctx) => {
return {
auth: {
provider: "my-provider",
loader: async (auth, provider) => {
return { /* auth data */ }
},
methods: [
{
type: "oauth",
label: "Login with My Service",
async authorize(inputs) {
return {
url: "https://auth.example.com/authorize",
instructions: "Click the link above",
method: "auto",
callback: async () => ({
type: "success",
access: "token",
refresh: "refresh_token",
expires: 3600
})
}
}
}
]
}
}
}Plugins can subscribe to comprehensive system events via the event hook:
| Category | Events |
|---|---|
| File | file.edited, file.watcher.updated |
| Session | session.created, session.deleted, session.idle, session.error, session.compacted |
| Message | message.updated, message.removed |
| Permission | permission.asked, permission.replied |
| Tool | tool.execute.before, tool.execute.after |
| LSP | lsp.updated, lsp.client.diagnostics |
| Shell | shell.env |
export const NotificationPlugin: Plugin = async ({ $, directory }) => {
return {
event: async ({ event }) => {
if (event.type === "session.idle") {
await $`notify-send "Session completed"`
}
},
}
}Local plugins and tools can use external npm packages via a config-level package.json:
{
"dependencies": {
"shescape": "^2.1.0"
}
}OpenCode runs bun install at startup to install these dependencies.
| Plugin | Purpose |
|---|---|
| opencode-helicone-session | Inject Helicone session headers |
| opencode-type-inject | Auto-inject TypeScript types |
| opencode-dynamic-context-pruning | Optimize token usage |
| opencode-devcontainers | Multi-branch isolation |
| oh-my-opencode | Pre-built tools, agents, LSP |
| opencode-supermemory | Persistent memory across sessions |
Browse more at awesome-opencode.
| Mechanism | Purpose |
|---|---|
| Plugins | Hook into lifecycle events, modify LLM behavior |
| Custom Tools | Define new LLM-callable capabilities |
| MCP Servers | Integrate external protocol-compliant tools |
| Skills | Reusable markdown instruction sets |
| Custom Agents | Specialized agent profiles |
| Auth Hooks | OAuth/API key for custom providers |
| Event System | React to system state changes |