Repository sits at the OpenCode config root rather than inside skills/ or
plugins/. OpenCode scans exactly those two subdirectories, and a .git
directory inside either one is picked up as a plugin and fails to load:
loading plugin id=.../plugins/.git entrypoint=.../plugins/.git/index
failed to load plugin ... AggregateError: 2 errors building .../plugins/.git/index
The watcher.ignore config option does not help: it only suppresses filesystem
update events, not plugin discovery, and the "-" disable prefix only applies to
plugins listed in opencode.json(c), not auto-discovered ones.
.gitignore admits only skills/ and plugins/ (plus itself). Personal config
(opencode.jsonc, cli.json, service.json) and node_modules stay local.
skills/opencode-plugins V2-only plugin authoring reference and 7 templates,
transcribed from the V2 docs with the URL on every file
plugins/searxng-search searxng_search tool over a self-hosted SearXNG JSON API
4.4 KiB
Templates
Starter files transcribed from the OpenCode V2 doc pages. Copy one and fill in the blanks rather than writing a plugin from scratch.
| Template | Use for | Source doc |
|---|---|---|
promise-plugin.ts |
Server plugin, Promise flavor (default) | https://opencode.ai/v2/docs/build/plugins |
effect-plugin.ts |
Server plugin, Effect-native | https://opencode.ai/v2/docs/build/plugins/effect/ |
rpc.ts |
RPC contract, Promise flavor (copy to src/rpc.ts) |
https://opencode.ai/v2/docs/build/plugins/rpc/ |
rpc-implementation.ts |
RPC registration + calls, Promise | https://opencode.ai/v2/docs/build/plugins/rpc/ |
rpc-effect.ts |
RPC contract, Effect flavor | https://opencode.ai/v2/docs/build/plugins/effect/rpc/ |
rpc-implementation-effect.ts |
RPC registration + calls, Effect | https://opencode.ai/v2/docs/build/plugins/effect/rpc/ |
tui-plugin.tsx |
TUI / CLI plugin | https://opencode.ai/v2/docs/build/plugins/cli/ |
opencode-plugin-config.jsonc |
plugins in opencode.json(c) |
https://opencode.ai/v2/docs/plugins |
package.json |
Publishing a plugin package (strict JSON) | https://opencode.ai/v2/docs/build/plugins |
package-json.md |
Publishing guidance, peer deps, entrypoints | build/plugins, build/plugins/effect, build/plugins/cli |
README.md |
This index | — |
package.json is strict JSON with no comments, because Node, bun, npm, and
bundlers parse it with a strict parser. Publishing guidance therefore lives in
package-json.md rather than inside the manifest.
The two RPC template pairs are not interchangeable: same contract shape,
different schema dialect, handler signature, and registration entrypoint. See the
Promise-vs-Effect table in ../references/rpc.md.
Before you write code
- Check the version:
opencode --version,opencode api get /api/info. - Pick the flavor. Default to Promise. Effect only if the project already uses Effect. TUI is a separate export. RPC is a contract plus a registration.
- Open the source doc in the table above and keep it open.
- Copy the template. Delete the sections you do not need.
- Verify every field name against that page. Do not guess.
Rules the templates encode
These are the constraints the pages state, not stylistic preferences. Note which are type-level rather than documented runtime failures.
- A
transformcallback is typed(editor) => voidin both flavors — no Promise, no Effect. - Tool
updatepreserves name and namespace. Assign a new schema or options object to replace one rather than mutating nested values. In-place edits inside anupdatecallback are fine — that is what the mutable draft is for. reload()replays transforms. It does not rerunsetup/effect.- Captured inputs are not watched. Load external data before the sync callback,
then call
reload()when it changes. - Use the effective tool name — including its namespace — for
updateandremove(example_greeting, notgreeting). Onlyaddcreates a tool. - A
contexthook covers the agent loop only. Register each request kind you actually need. - Result shape is per operation, not per flavor:
storage.getreturns a value,model.list()is enveloped. Copy the page's example for the call you use. - Effect plugins use
effect, notsetup. The two flavors do not have the same context members or the same hook set. - Relative plugin paths in config resolve from the config file, not the working directory.
- Registering an event subscription without tearing it down leaks it.
forkScopedhandles this in Effect;AbortControllerin Promise. package.jsonmust be strict JSON —//comments there breakbun,npm, and every bundler.opencode.jsoncmay use them.
Anything not in a template
The pages carry more detail than fits here. When you need something not in these files, go to the source doc rather than improvising:
- Every
Agent.Info,Provider.Info,Model.Info,Session.Info,Tool.Content,Mcp.Server,Skill.Info,Vcs.Info,Reference.Info,Integration.Info,Connection.Info,Form.Answer,Command.Info,Plugin.Info,WebSearch.*,Worktree*shape: https://opencode.ai/v2/docs/api - The exact contract of the running server:
/openapi.jsonon that server. - Hook event fields: the
Referencecode block at the end of each section on the relevant page.