# A practical guide to Herdr plugins

> Install, inspect, configure, run, and build Herdr plugins, with practical commands and the security checks to make before trusting one.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2026-08-22 | Topics: [AI](https://flaviocopes.com/tags/ai/) | Canonical: https://flaviocopes.com/herdr-plugins/

Herdr plugins are executable workflow packages.

They can create a project layout, add an action, react to an event, open a terminal pane, or connect Herdr to another local tool.

A plugin can be written in Bash, JavaScript, PowerShell, Lua, Rust, or any other language your machine can run.

There is no separate plugin SDK.

Herdr provides the host: installation, the manifest, keybindings, events, panes, logs, and runtime context. The plugin calls the normal Herdr CLI or socket API when it needs to control the workspace.

In this guide we’ll install a plugin, inspect it, run an action, and build a small local plugin.

For the broader terminal and agent model, read my [Herdr deep dive](https://flaviocopes.com/herdr/) first.

## Find a plugin

Community plugins are listed at [herdr.dev/plugins](https://herdr.dev/plugins).

The marketplace is an automatic index of public GitHub repositories tagged with `herdr-plugin`. A listing is not a security review.

Before installing anything:

1. Open the repository.
2. Read `herdr-plugin.toml`.
3. Check the build and runtime commands.
4. Inspect the scripts or source code those commands run.
5. Decide whether you trust the author.

Herdr plugins run as your user. They inherit your environment and can call the full Herdr CLI. They are not sandboxed.

Treat one like a shell script or editor extension you are about to run locally.

## Install a plugin from GitHub

The install command accepts GitHub shorthand:

~~~bash
herdr plugin install owner/repository
~~~

If the repository contains several plugins, add the subdirectory:

~~~bash
herdr plugin install owner/repository/path/to/plugin
~~~

For example, the official documentation uses this community example:

~~~bash
herdr plugin install \
  ogulcancelik/herdr-plugin-examples/agent-telegram-notify
~~~

In an interactive terminal, Herdr shows the source and the commands it plans to run before asking for confirmation.

Do not add `--yes` the first time you install a plugin. That flag skips the interactive confirmation and is better reserved for a source and revision you have already reviewed.

You can pin a Git ref:

~~~bash
herdr plugin install \
  owner/repository/path/to/plugin \
  --ref v1.2.0
~~~

Herdr uses Git, runs the build commands declared in the manifest, stores the checkout in a managed directory, and registers the plugin.

If a build command fails, the plugin is not registered.

## List and inspect installed plugins

List everything:

~~~bash
herdr plugin list
~~~

Ask for structured output when you are scripting:

~~~bash
herdr plugin list --json
~~~

Filter by plugin ID:

~~~bash
herdr plugin list --plugin examples.agent-telegram-notify
~~~

The plugin ID comes from its manifest. It does not have to match the GitHub repository name.

Plugin installation and enabled state are global for the current user. Install a plugin in one named Herdr session and it is available in the others too.

## Configure a plugin

Each plugin gets a stable configuration directory.

Print it with:

~~~bash
herdr plugin config-dir examples.agent-telegram-notify
~~~

Put user-editable settings such as an `.env` file there when the plugin documentation asks for them.

Do not put credentials inside the managed plugin checkout. Reinstalling a GitHub plugin replaces that checkout.

Herdr also gives a plugin a separate state directory at runtime. The plugin owns the files and data format inside it.

## Run plugin actions

List the actions a plugin exposes:

~~~bash
herdr plugin action list \
  --plugin examples.agent-telegram-notify
~~~

Invoke one:

~~~bash
herdr plugin action invoke \
  examples.agent-telegram-notify.send-test
~~~

The exact action IDs depend on the manifest.

If action names collide across plugins, use the fully qualified form:

~~~text
plugin.id.action
~~~

You can bind an action in `~/.config/herdr/config.toml`:

~~~toml
[[keys.command]]
key = "prefix+l"
type = "plugin_action"
command = "example.layout.apply"
description = "apply layout"
~~~

Reload the configuration:

~~~bash
herdr server reload-config
~~~

## Open a plugin pane

A plugin can declare terminal pane entrypoints.

List or read the plugin documentation to find the entrypoint ID, then open it:

~~~bash
herdr plugin pane open \
  --plugin example.layout \
  --entrypoint board
~~~

Herdr supports several placements:

- `overlay` for a temporary zoomed pane
- `popup` for a modal terminal that does not change the layout
- `split` beside an existing pane
- `tab` in a new tab
- `zoomed` as a normal zoomed Herdr pane

You can override the manifest placement:

~~~bash
herdr plugin pane open \
  --plugin example.layout \
  --entrypoint board \
  --placement popup \
  --width 80% \
  --height 20
~~~

A popup is intentionally temporary. Normal split, tab, zoomed, and overlay panes participate in the usual Herdr pane APIs.

## Read plugin logs

If an action fails, inspect its command log:

~~~bash
herdr plugin log list \
  --plugin example.layout \
  --limit 20
~~~

The log tells you which declared command ran and how it exited.

It does not interpret the tool’s output for you. A failing Node, Bash, or Rust command is still debugged as that kind of program.

## Disable, enable, and remove plugins

Temporarily disable a plugin:

~~~bash
herdr plugin disable example.layout
~~~

Enable it again:

~~~bash
herdr plugin enable example.layout
~~~

Uninstall a GitHub-managed plugin:

~~~bash
herdr plugin uninstall example.layout
~~~

You can also use the original source shorthand:

~~~bash
herdr plugin uninstall owner/repository/path/to/plugin
~~~

There is no separate plugin update command in the current plugin system. Reinstall the GitHub source to refresh its managed checkout.

## Build a small local plugin

Let’s create an action that lists the current Herdr workspaces.

The directory has two files:

~~~text
workspace-tools/
  herdr-plugin.toml
  index.js
~~~

The manifest is:

~~~toml
id = "example.workspace-tools"
name = "Workspace Tools"
version = "0.1.0"
min_herdr_version = "0.7.0"
description = "Small workspace helpers"
platforms = ["linux", "macos", "windows"]

[[actions]]
id = "list-workspaces"
title = "List workspaces"
contexts = ["workspace"]
command = ["node", "index.js"]
~~~

Herdr runs commands as argv arrays. It does not send them through a shell, so shell expansion does not happen unless the declared command starts a shell itself.

The JavaScript file can call the installed Herdr binary:

~~~js
const { spawnSync } = require('node:child_process')

const herdr = process.env.HERDR_BIN_PATH ?? 'herdr'

const result = spawnSync(
  herdr,
  ['workspace', 'list'],
  {
    encoding: 'utf8',
    stdio: ['ignore', 'pipe', 'pipe'],
  },
)

process.stdout.write(result.stdout)
process.stderr.write(result.stderr)
process.exit(result.status ?? 1)
~~~

`HERDR_BIN_PATH` is better than hardcoding `herdr`. It points to the binary that owns the running session and works across Unix sockets and Windows named pipes.

## Link the working directory

During development, link the local directory:

~~~bash
herdr plugin link /absolute/path/to/workspace-tools
~~~

List and invoke the action:

~~~bash
herdr plugin action list \
  --plugin example.workspace-tools

herdr plugin action invoke \
  example.workspace-tools.list-workspaces
~~~

Read the log if it fails:

~~~bash
herdr plugin log list \
  --plugin example.workspace-tools
~~~

When you are done testing, unlink it:

~~~bash
herdr plugin unlink example.workspace-tools
~~~

Unlinking removes the registration and leaves your local files alone.

Notice that `plugin link` does not run manifest build commands. You are working from a live checkout, so you build it yourself.

## What a manifest can declare

A plugin manifest can currently define:

| Entry | Use |
| --- | --- |
| `[[build]]` | Prepare a GitHub-installed plugin |
| `[[startup]]` | Run one initialization command after server restore |
| `[[actions]]` | Expose commands that people, keys, or tools can invoke |
| `[[events]]` | React to known Herdr events |
| `[[panes]]` | Open a terminal interface managed by the plugin |
| `[[link_handlers]]` | Route matching terminal links to a plugin action |

Startup hooks are one-shot initialization commands, not supervised background services.

Actions and panes are declared in the manifest. Runtime action registration and native non-terminal plugin interfaces are not part of the current v1 surface.

## Runtime context

Herdr starts plugin commands inside the plugin directory and injects useful variables, including:

~~~text
HERDR_BIN_PATH
HERDR_ENV
HERDR_PLUGIN_ID
HERDR_PLUGIN_ROOT
HERDR_PLUGIN_CONFIG_DIR
HERDR_PLUGIN_STATE_DIR
HERDR_PLUGIN_CONTEXT_JSON
HERDR_WORKSPACE_ID
HERDR_TAB_ID
HERDR_PANE_ID
~~~

Not every invocation has every workspace, tab, or pane value.

`HERDR_PLUGIN_CONTEXT_JSON` can include the focused pane, worktree, agent, selected text, or clicked URL. Use it when the action needs more than the common IDs.

## My recommendation

Start by installing one small plugin from a repository you have read.

Learn these four commands first:

~~~bash
herdr plugin list
herdr plugin action list --plugin ID
herdr plugin action invoke ID.ACTION
herdr plugin log list --plugin ID
~~~

Then build one local action and link it.

Herdr plugins are deliberately simple. The manifest describes how a workflow connects to Herdr, while the actual program remains a normal command you can run, inspect, and debug.

The complete manifest reference is in the [official Herdr plugin guide](https://herdr.dev/docs/plugins/). If you want an agent to operate the workspace for you, read [how to use the Herdr skill](https://flaviocopes.com/herdr-skills/).
