Building a plugin
At its core a plugin is a web server in a container plus a declarative manifest. The manifest says what the plugin is; the runtime handles ports, health, env injection, and per-env enablement by convention. This page covers both.
The runtime contract
A few things are fixed conventions — you don't declare them, you just follow them:
- Your HTTP server listens on port 8080.
- A health probe hits
GET /and expects a non-5xx response within about 15 seconds (a redirect to your UI path is fine). - The platform always injects
ENV_IDandWORKSPACE_ID. If you ask for Postgres storage, it also injectsDATABASE_URLandPGSCHEMA.
The manifest
The manifest is validated when the plugin is installed and re-checked on every boot. The fields:
id: com.acme.roadmap # reverse-DNS-ish; lowercase alnum + dot + dash
name: Roadmap # display name
description: A per-env task board the agent can drive.
version: 1.2.0
icon: list-todo # Lucide icon name (optional)
image: ghcr.io/acme/roadmap:1.2.0 # OCI image ref
scope: env # env | workspace | global (default: env)
storage:
kind: shared-postgres # none | shared-postgres (default: none)
ui:
path: /ui # where the iframe loads (default: /)
websocket: false # true if your UI needs WS upgrades
mcp:
enabled: true # expose tools to the env-chat agent
path: /mcp # where your MCP endpoint lives (default: /mcp)
agentInstructions: >
This plugin tracks the project roadmap. Before changing project
scope, open a roadmap item with the create_item tool.Field notes
- id— ends up in URLs and Docker labels, so it's restricted to lowercase alphanumerics, dots, and dashes, with no leading or trailing punctuation.
- scope / storage — see Scopes & storage. Both default to the simplest choice: one container per env, stateless.
- ui.path — most plugins serve their UI at
/uito keep it off the API root. Setwebsocket: trueif the panel needs live updates. - mcp— opt in to give the agent tools. When enabled, the platform forwards the agent's tool calls to your MCP endpoint.
- agentInstructions— short guidance prepended to the env-chat system prompt whenever your plugin is enabled. Without it, the agent sees your tools but doesn't know the rules around them. Keep it brief.
Adding a UI
Serve a web UI at your ui.path. In an env, withvibe renders it in an iframe inside the plugin panel once the container is started and healthy. Because ENV_ID and WORKSPACE_ID are in your environment, the same image can scope its data correctly across many envs.
Adding agent tools
Set mcp.enabled: true and serve an MCP endpoint at mcp.path. When the plugin is enabled and running in an env, the agent's tool calls are forwarded there and the tools appear to the agent as mcp__plugin_<id>__<tool>. Use agentInstructionsto tell the agent when to use them — that pairing of tools plus instructions is what makes a plugin like Voter's team-vote gate actually change agent behavior.
Storage
If your plugin needs a database, set storage.kind: shared-postgres. The platform provisions a dedicated Postgres role and schema for your plugin and injects DATABASE_URL and PGSCHEMA. That role is scoped to your plugin's own database only — it can't reach the withvibe app data.
Publishing to the marketplace
Once your plugin builds and runs, list it on the public catalog so any withvibe deployment can one-click install it. Browse what's already there at the marketplace, then sign in to the publisher portal with GitHub to submit yours.
- Sign in at /sign-in with your GitHub account — your GitHub identity becomes your verified publisher badge.
- Submit your plugin from the publisher dashboard by pasting its YAML manifest. Make sure your
imageref points at a pullable OCI image. - Wait for review— each version goes through a review and scan before it shows up in the catalog. Once it's
ready, admins can install it from Admin → Plugins → Marketplace.
Pushing an update is the same flow: bump version in the manifest and submit a new version — it goes through review independently, and the catalog serves the latest approved one.