Skip to main content
A plugin is a directory in Qoder CLI that bundles commands, sub-agents, Skills, Hooks, MCP servers, and other extensions for installation, enable/disable management, and sharing. A plugin directory may contain one or more of these resources, and the CLI auto-discovers and loads them after installation.

Quick Start

The following example creates a minimal plugin containing a single Skill and installs it from a local directory.

1. Create the plugin directory

mkdir -p ~/my-plugin/.qoder-plugin
mkdir -p ~/my-plugin/skills/hello

2. Write the manifest

Declaring a plugin.json is recommended for every plugin so it has stable metadata; at minimum include name (see Manifest Fields below): ~/my-plugin/.qoder-plugin/plugin.json:
{
  "name": "my-plugin",
  "version": "0.1.0",
  "description": "My first plugin"
}

3. Add a Skill

~/my-plugin/skills/hello/SKILL.md:
---
name: hello
description: Greet the user. Use when the user says "say hi".
---

# Hello Skill

Greet the user warmly.

4. Install

qodercli plugins install ~/my-plugin
After seeing Plugin "my-plugin@local" installed successfully. Run /plugins reload to apply., restart the CLI or run /plugins reload in the TUI to start using the Skill.

Plugin Directory Layout

.qoder-plugin/plugin.json is the recommended location for the manifest. When omitted, the CLI still loads the directory by convention and uses the directory name as the plugin name. Convention directories are auto-discovered when present, otherwise ignored.
my-plugin/
├── .qoder-plugin/
│   └── plugin.json        # Recommended: manifest (declares name/version/etc.)
├── commands/              # Custom commands (.md files or subdirectories)
├── agents/                # Custom sub-agents
├── skills/                # Custom Skills
├── hooks/
│   └── hooks.json         # Hook configuration
├── output-styles/         # Output styles
├── bin/                   # Plugin executables
└── .mcp.json              # MCP servers shipped with this plugin
Convention directory behavior:
Directory / FilePurpose
commands/Register custom slash commands; same structure as ~/.qoder/commands/
agents/Register custom sub-agents
skills/Register Skills; same structure as ~/.qoder/skills/
hooks/hooks.jsonHook configuration; uses the { "hooks": ... } wrapper, where the inner hooks value matches the hooks field in settings.json
output-styles/Custom output styles
bin/Plugin executables
.mcp.jsonMCP server declarations bundled with the plugin

Manifest Fields

Only name is required in plugin.json; other fields are optional.
FieldRequiredDescription
nameYesUnique plugin identifier; cannot contain spaces; kebab-case recommended (e.g., my-plugin)
versionNoSemantic version (e.g., 1.0.0)
descriptionNoBrief description
authorNoAuthor information
homepageNoDocumentation or homepage URL
repositoryNoSource repository URL
licenseNoSPDX license identifier (e.g., MIT)
keywordsNoTags for discovery and categorization
Advanced: the manifest can also explicitly declare commands / agents / skills / hooks / outputStyles to override the default directory conventions or use inline content (note the manifest field uses camelCase outputStyles, while the convention directory remains output-styles/). When not declared, the CLI auto-discovers via the conventions above.

Installation Scope

Plugins can be installed at three scopes:
ScopeDescriptionUse case
userGlobally available; applies to all of the current user’s projects (default)Personal frequently-used plugins
projectApplies only to the current project; written to project-level settings.json, can be committed to git for team sharingTeam-shared, project-specific plugins
localApplies only to the current project; written to project-local settings.local.json, recommended to add to .gitignoreLocal experimental plugins

Commands

Plugin commands live under the qodercli plugins subcommand group, aliased as plugin.

Install: plugins install

Install a plugin from a local directory path:
qodercli plugins install ~/my-plugin
qodercli plugins install ./relative/path/to/plugin
qodercli plugins install /abs/path/to/plugin --scope project
Argument / OptionDescription
<plugin>Local plugin directory path (absolute, relative, or ~/...)
-s, --scope <scope>Installation scope: user (default), project, local
After installation, restart the CLI or run /plugins reload in the TUI to apply changes.

Uninstall: plugins uninstall

qodercli plugins uninstall my-plugin
qodercli plugins uninstall my-plugin --scope project --keep-data
Aliases: remove / rm.
Argument / OptionDescription
<plugin>Installed plugin name
-s, --scope <scope>Scope to uninstall from: user (default), project, local
--keep-dataPreserve the plugin’s data directory

Enable / Disable: plugins enable / plugins disable

qodercli plugins enable my-plugin
qodercli plugins disable my-plugin
qodercli plugins enable my-plugin --scope project
qodercli plugins disable --all
Argument / OptionDescription
<plugin>Installed plugin name
-s, --scope <scope>Scope to write to; auto-detected if omitted
-a, --all(disable only) Disable all enabled plugins in the chosen scope
Enable / disable is implemented by updating the enabledPlugins field in the corresponding settings.json. Disabled plugins are not loaded in new sessions.

List: plugins list

qodercli plugins list
qodercli plugins list --json
qodercli plugins list --plugin-dir ./local-plugins ./more-plugins
OptionDescription
--jsonOutput as JSON
-o, --output-format <format>text or json (equivalent to --json)
--plugin-dir <dirs...>Additional directories to scan and merge into the listing (does not install)

Validate: plugins validate

Validate that a local plugin directory matches the conventions; useful during development:
qodercli plugins validate ~/my-plugin
The command lists the commands, Skills, Hooks, and other components it discovers, and prints a notice when no convention subdirectory exists. Note: validate does not fail in that case — but plugins install for a local plugin requires at least one recognizable component or resource (either a convention directory or a resource explicitly declared in the manifest).
Recommended: always declare name, version, etc. in .qoder-plugin/plugin.json. This is the recommended way to organize a Qoder plugin — without it, the plugin can only be identified by its directory name in plugins list, enabledPlugins, and other places, which is fragile across environments.

The enabledPlugins Setting

Enable / disable state is stored in the enabledPlugins field of settings.json:
{
  "enabledPlugins": {
    "my-plugin@local": true,
    "another-plugin@local": false
  }
}
  • true: enables the plugin
  • false: explicitly disables the plugin
The configuration key must match the installed plugin ID exactly (locally installed plugins have IDs of the form name@local). Run plugins list to see each plugin’s identifier.
Prefer plugins enable / plugins disable over hand-editing this field — the commands handle scope selection, dependency resolution, and other details for you.

Writing Plugin Hooks

A plugin may declare its own Hooks in hooks/hooks.json. The file uses a wrapped shape: a top-level object with a hooks field whose value matches the hooks field in settings.json:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$QODER_PLUGIN_ROOT/scripts/check.sh"
          }
        ]
      }
    ]
  }
}
Note this differs from settings.json: settings.json uses the bare hooks field, while a plugin’s hooks/hooks.json requires the extra { "hooks": ... } wrapper.
When executed, plugin Hooks receive two extra environment variables:
VariableDescription
QODER_PLUGIN_ROOTInstallation root of the current plugin
QODER_PLUGIN_DATAData directory of the current plugin (separate from the install dir to preserve state across upgrades)
See Hooks for more on writing Hooks.