What it is

A plugin is a standalone code module that adds functionality to OpenCode without modifying its core. Plugins subscribe to events (via hooks) and can add custom tools, inject environment variables, or run side effects.

What it is in real OpenCode terms

A plugin is a JavaScript or TypeScript file that exports a default function. That function receives context about the project and returns an object containing hooks and optionally custom tools. OpenCode loads plugins from local directories or from npm at startup.

The plugin function is called with a set of parameters:

export default function({ project, client, $, directory, worktree }) {
  // project: project metadata
  // client: OpenCode API client
  // $: shell helper (for running commands)
  // directory: current working directory
  // worktree: git worktree path

  return {
    hooks: { /* event handlers */ },
    tools: [ /* custom tool definitions */ ],
  };
}

Why it exists

Anatomy / main elements

A plugin file has three key parts:

  1. Exported function - the entry point, receives project context
  2. Hooks object - event handlers (see the Hook concept)
  3. Tools array (optional) - custom tool definitions via the tool() helper
import { tool } from "@opencode-ai/plugin";

export default function({ directory }) {
  return {
    hooks: {
      "file.edited": async (event) => {
        console.log("Edited:", event.path);
      },
    },
    tools: [
      tool({
        name: "my-custom-tool",
        description: "Does something specific",
        parameters: { type: "object", properties: {} },
        execute: async (args) => {
          return "result from custom tool";
        },
      }),
    ],
  };
}

Where it lives

Plugins are loaded from four locations, in this order:

  1. Global config plugins - listed in ~/.config/opencode/opencode.json under "plugin"
  2. Project config plugins - listed in opencode.json under "plugin"
  3. Global plugins directory - files in ~/.config/opencode/plugins/
  4. Project plugins directory - files in .opencode/plugins/

For npm plugins, add a package.json to .opencode/ with your dependencies. OpenCode auto-installs them via Bun at startup.

How to create one

Local plugin (simplest):

# Create the plugins directory
mkdir -p .opencode/plugins

# Create your plugin file
cat > .opencode/plugins/my-plugin.ts <<'EOF'
export default function({ directory }) {
  return {
    hooks: {
      "session.created": async () => {
        console.log("Session started in", directory);
      },
    },
  };
}
EOF

npm plugin:

# Add a package.json to .opencode/
cat > .opencode/package.json <<'EOF'
{
  "dependencies": {
    "@opencode-ai/plugin": "latest"
  }
}
EOF

# Reference the npm package in opencode.json
cat > opencode.json <<'EOF'
{
  "plugin": ["my-opencode-plugin"]
}
EOF

OpenCode installs npm dependencies via Bun automatically on startup.

Minimal example

A plugin that prints a message when a session starts and adds a custom tool:

// .opencode/plugins/greet.ts
import { tool } from "@opencode-ai/plugin";

export default function({ directory }) {
  return {
    hooks: {
      "session.created": async () => {
        console.log("Welcome! Session active in:", directory);
      },
    },
    tools: [
      tool({
        name: "greet",
        description: "Returns a greeting",
        parameters: {
          type: "object",
          properties: {
            name: { type: "string", description: "Who to greet" },
          },
          required: ["name"],
        },
        execute: async ({ name }) => {
          return `Hello, ${name}!`;
        },
      }),
    ],
  };
}

Related concepts

Common confusion

Is a plugin the same as a hook? No. A plugin is the module. A hook is one of the things a plugin can return. A plugin can return hooks, tools, or both.
Do I need TypeScript? No. Plain JavaScript works fine. TypeScript gives you autocompletion and type safety via @opencode-ai/plugin, but it is optional.
What is load order? Global config → project config → global plugins dir → project plugins dir. This means project-level plugins can override global ones.