Buoy plugins

Plugins let you teach Buoy about your CRDs (or extend its handling of built-in kinds) without forking the app. A plugin is one TypeScript file (.tsx). Drop it into the plugins directory and Buoy hot-reloads it across every open tab.

What plugins can do:

  • Detail tabs — add a tab to the detail view for matching (group, kind) pairs. The tab body is a React render(ctx) callback that returns JSX built from Buoy’s widget catalog (<KeyValue>, <Table>, <Pills>, <Conditions>, <RelatedList>, …). Live cluster reads inside the tab use useObject / useList hooks.
  • Actions — add menu items in the detail-view kebab and the list-view bulk actions menu. Each action’s run(ctx) callback issues patches via ctx.mergePatch / strategicPatch / jsonPatch / statusPatch / delete, gated by a confirm modal.
  • List columns — declare default -L / -F / --ann columns and promoted-wide columns for a kind, so mycrd shows the right columns out of the box.
  • Slash commands — register /<name> commands (buoy.addCommand) that do arbitrary async work: navigate, mutate, prompt the user, run background tasks.
  • Custom views — register /<name> to open a full top-level view (buoy.addView), like the built-in /plugins or /contexts.
  • Async helpersnotify(msg) for toasts, runInBackground(label, fn) for spinners, prompt({fields}) for modal inputs, useAsync(fn, deps) for one-shot async work inside render. See commands.md.

What plugins can’t do (intentionally):

  • Replace or hide built-in tabs and actions. Plugins are purely additive — a Pod’s Logs tab always shows up, no matter what plugins are installed.
  • Mutate the cluster outside the patch / delete primitives. There’s no exec, port-forward, or arbitrary HTTP — if you need that, you’re outside the plugin model.
  • Add Tauri commands or talk to non-Kubernetes services.

Contents


Your first plugin

Scaffold from the command bar with /plugin new <name> (or Settings → Plugins → New TypeScript Plugin). The scaffold writes <name>.tsx plus buoy-sdk.d.ts and tsconfig.json into the plugins dir so any TS-aware editor gets full autocomplete with zero npm install.

A minimal plugin looks like this:

/// <reference path="./buoy-sdk.d.ts" />

export default (buoy: Buoy) => {
  buoy.meta({
    name: "configmap-data",
    version: "0.1.0",
    description: "Quick view of ConfigMap entries",
  });

  buoy.addDetailTab({
    match: { group: "", kind: "ConfigMap" },
    id: "data",
    label: "Data",
    render: ({ obj }) => {
      const data = obj?.data ?? {};
      const keys = Object.entries(data);
      return (
        <>
          <KeyValue title="Summary">
            <Row label="Entries" value={keys.length} />
            <Row label="Binary" value={Object.keys(obj?.binaryData ?? {}).length} />
          </KeyValue>
          {keys.length === 0 && (
            <Banner tone="warn" text="This ConfigMap is empty." />
          )}
          <Table
            title="Entries"
            rows={keys}
            columns={[
              { header: "Key", cell: ([k]) => k },
              { header: "Bytes", cell: ([, v]) => (v as string).length },
            ]}
          />
        </>
      );
    },
  });

  buoy.addAction({
    match: { group: "", kind: "ConfigMap" },
    id: "touch",
    label: "Touch",
    confirm: {
      phrase: (obj) => obj.metadata.name,
      title: (obj) => `Touch ${obj.metadata.name}`,
    },
    run: async (ctx) => {
      await ctx.mergePatch({
        metadata: {
          annotations: { "example.com/touched-at": new Date().toISOString() },
        },
      });
    },
  });
};

A few things to notice:

  • match: { group: "", kind: "ConfigMap" } — empty group is the core API group. apps / argoproj.io / my.crd.io all work the same way for other kinds.
  • The render(ctx) callback is a regular React function — it receives { obj, context, namespace, tick, openObject, openNamespace } and returns JSX. Anything you can write in TypeScript works here: if, .map(), destructuring, helper functions you define elsewhere in the same file.
  • Inside render, you can call useObject(...) and useList(...) to embed live cluster reads. They’re real React hooks; their results re-render the tab as the underlying watch updates.
  • Actions are async callbacks. ctx.mergePatch / strategicPatch / jsonPatch / statusPatch / delete are pre-bound to the target object — you only supply the patch body. Confirm fields can be plain strings or (obj) => string callbacks (use a callback when the confirm needs object data, like the name to type).

Built-in plugins

Buoy ships with a small set of plugins embedded in the binary. They show up in the /plugins view with a Built-in badge alongside any user plugins you’ve installed:

  • secret-decoded — adds a Decoded tab to every Secret, base64-decoding each entry under data and rendering as a block. Same access model as the YAML tab; just saves a copy/paste-and-decode dance.

Built-ins can’t be uninstalled. To replace one, write a user plugin with the same meta.name — yours wins because user plugins register later.

Installation paths

Plugins live under:

OSPath
macOS~/Library/Application Support/com.buoy.app/plugins/
Linux~/.config/com.buoy.app/plugins/
Windows%APPDATA%\com.buoy.app\plugins\

You can install in three ways:

  1. Drop a .tsx file at the root of the plugins dir. The basename becomes the plugin’s id.
  2. Drop a folder (e.g. my-org-crds/plugin.tsx). Useful when a plugin ships with a README or example screenshots.
  3. /plugin install <https://url> from the Buoy command bar. The file is fetched and cached under plugins/.cache/. HTTPS only — http:// and file:// are rejected because anything in the plugin dir can issue patches against your cluster.

The /plugins view (also reachable from Settings → Plugins) lists everything installed, shows compile errors inline, and offers uninstall / enable / disable toggles.

Hot reload

Buoy watches the plugins directory and re-compiles on every change. Edit a .tsx, save, look at the open detail view — your edits apply without restarting Buoy or refreshing the view. Compile errors surface in the /plugins view rather than silently dropping the plugin.

Sharing plugins

The simplest distribution is a public gist. Share the gist URL and recipients install with /plugin install <gist-raw-url>. For more polished distribution, host the .tsx in a repo and link the raw GitHub URL. Buoy doesn’t have a registry yet.

Security model

Plugin code runs on the main thread with the same trust level as Buoy itself — there is no sandbox. Plugins can do anything you could do with kubectl patch and kubectl delete against your active context (and as your current impersonation, if /as is active). They cannot exec, port-forward, read secrets they couldn’t already read, or talk to anything outside the kube API. Every mutation goes through Buoy’s standard ConfirmModal — the same red typing-required dialog the built-in Delete uses.

When installing from a URL there’s no signature check yet — install plugins from sources you trust, the same way you’d kubectl apply a manifest you found online.

Where to go next


Edit this page on GitLab