Pular para o conteúdo

Writing a plugin

Este conteúdo não está disponível em sua língua ainda.

This guide builds a minimal plugin end to end. For the conceptual model behind each step, see Plugin System.

Before writing code, pick one mode and stick to it:

If your plugin needs…UseAPI lives in
DB connections, SSE/WebSocket, in-memory cache, cron, shared statePersistentplugin.ts (routes)
Stateless CRUD, file ops, per-request isolationServerlessindex.ts (routes)
plugins/plugin-hello/
├── manifest.yaml
├── plugin.ts
└── package.json
package.json
{
"name": "@buntime/plugin-hello",
"type": "module",
"exports": { "bun": "./plugin.ts", "default": "./dist/plugin.js" }
}
manifest.yaml
name: "@buntime/plugin-hello"
base: "/hello"
enabled: true
pluginEntry: dist/plugin.js
menus:
- { title: Hello, icon: lucide:hand, path: /hello }

The base must match /[a-zA-Z0-9_-]+ and cannot be a reserved path (/api, /health, /.well-known). A hook-only plugin with no routes should omit base entirely.

A plugin module’s default export is a factory returning a PluginImpl.

plugin.ts
import { Hono } from "hono";
import type { PluginImpl } from "@buntime/shared/types";
export default function helloPlugin(config: Record<string, unknown>): PluginImpl {
const routes = new Hono()
.get("/api/greeting", (c) => c.json({ message: "Hello from Buntime" }));
return {
routes, // mounted at /hello/api/greeting
onInit(ctx) {
ctx.logger.info("hello plugin initialized");
},
onShutdown() {
// flush caches, close connections, clear timers
},
};
}

Config flows from Helm/ConfigMap into Bun.env, with the manifest as a fallback. Always read — never write — with this precedence:

const apiKey = Bun.env.HELLO_API_KEY ?? (config.apiKey as string) ?? "default";

To expose functionality to other plugins, return it from provides(). Consumers declare a dependency and call ctx.getPlugin().

// provider
export default (): PluginImpl => {
const cache = new Map<string, string>();
return { provides: () => ({ get: (k: string) => cache.get(k) }) };
};
// consumer (manifest: dependencies: ["@buntime/plugin-hello"])
onInit(ctx) {
const hello = ctx.getPlugin<{ get(k: string): string | undefined }>("@buntime/plugin-hello");
}

Plugin tests live next to plugin.ts as plugin.test.ts and use bun:test. Mock the PluginContext and exercise the Hono app via app.fetch:

import { describe, expect, it } from "bun:test";
import helloPlugin from "./plugin.ts";
describe("plugin-hello", () => {
it("serves a greeting", async () => {
const plugin = helloPlugin({});
const res = await plugin.routes!.request("/api/greeting");
expect(res.status).toBe(200);
expect(await res.json()).toEqual({ message: "Hello from Buntime" });
});
});

See Testing plugins for the full mock factories.

Terminal window
bun run --filter "@buntime/plugin-hello" build # produce dist/plugin.js
# then, against a running runtime:
curl -X POST http://localhost:8000/api/plugins/reload

reload re-scans plugin directories, re-sorts by dependency, re-runs onInit, and refreshes the live route table — no process restart.