| name | browse-plugins |
| description | Create plugins for the browse CLI. Use when the user wants to extend browse with custom commands, add lifecycle hooks, or build a browse plugin. Triggers include: 'create a plugin', 'add a command to browse', 'extend browse', 'browse plugin', 'hook into browse'. |
Browse Plugin Authoring
What a plugin is
A plugin is a TypeScript or JavaScript file that default-exports a BrowsePlugin object. It can add custom commands, custom flow reporters, and hook into the lifecycle of any browse command.
Plugin structure
import type { BrowsePlugin } from "browse/plugin";
const plugin: BrowsePlugin = {
name: "my-plugin",
version: "1.0.0",
commands: [],
reporters: [],
hooks: {},
};
export default plugin;
Type definitions
All types are in src/plugin.ts. The key types:
CommandContext
Passed to every command handler:
type CommandContext = {
page: Page;
context: BrowserContext;
config: BrowseConfig | null;
args: string[];
sessionState: Record<string, unknown>;
request: { session?: string; json?: boolean; timeout?: number };
};
PluginCommand
type PluginCommand = {
name: string;
summary: string;
usage: string;
flags?: string[];
timeoutExempt?: boolean;
handler: (ctx: CommandContext) => Promise<Response>;
};
PluginHooks
type PluginHooks = {
init?: (config: BrowseConfig | null) => Promise<void>;
beforeCommand?: (cmd: string, ctx: CommandContext) => Promise<Response | void>;
afterCommand?: (cmd: string, ctx: CommandContext, response: Response) => Promise<void>;
cleanup?: () => Promise<void>;
};
CustomReporter
type CustomReporter = {
name: string;
render: (ctx: {
flowName: string;
results: StepResult[];
durationMs: number;
}) => string;
};
Custom reporters become available through browse flow --reporter <name> and browse test-matrix --reporter <name>.
Response
type Response = { ok: true; data: string } | { ok: false; error: string };
Step-by-step: creating a plugin
-
Create the file — e.g. plugins/my-plugin.ts
-
Define at least one command or hook:
import type { BrowsePlugin } from "../src/plugin.ts";
const plugin: BrowsePlugin = {
name: "my-plugin",
version: "1.0.0",
commands: [
{
name: "my-cmd",
summary: "Does something useful",
usage: `browse my-cmd [--json]
Flags:
--json Output as JSON`,
flags: ["--json"],
handler: async (ctx) => {
const url = ctx.page.url();
if (ctx.request.json) {
return { ok: true, data: JSON.stringify({ url }) };
}
return { ok: true, data: `Current page: ${url}` };
},
},
],
};
export default plugin;
- Register in
browse.config.json:
{
"environments": {},
"plugins": ["./plugins/my-plugin.ts"]
}
- Test it:
browse my-cmd
browse my-cmd --json
browse help my-cmd
Registration
Plugins are discovered from two sources:
- Config file —
"plugins" array in browse.config.json. Relative paths resolve from the config file's directory. Bare names (e.g. "browse-plugin-foo") resolve as npm packages.
- Global directory —
~/.browse/plugins/ — any .ts or .js files are auto-loaded.
Key behaviours
- Command names must be unique — collisions with built-in commands or other plugins are rejected at load time with a warning.
- Reporter names must be unique — collisions with built-in reporters or other plugin reporters are rejected at load time with a warning.
- Errors are isolated — a throwing handler returns
{ ok: false, error }, never crashes the daemon. Hook errors are caught similarly.
sessionState persists per session — use it to track state across commands. It resets when the session is closed.
beforeCommand can short-circuit — return a Response to prevent the command from running.
afterCommand is read-only — observe the response but cannot mutate it.
init failures are non-fatal — the plugin's commands and hooks still register.
Example plugins
See examples/plugin-example.ts for a working example with a command and lifecycle hooks.
Browse also ships official starter plugins under examples/plugins/:
examples/plugins/slack/index.ts
examples/plugins/discord/index.ts
examples/plugins/jira/index.ts
Use them as references when building integrations around webhooks or external issue trackers.
Full documentation
See docs/plugins.md for the complete guide including publishing plugins as npm packages.