For the complete documentation index, see llms.txt.
Skip to main content
Version: 8.9

Extend c8ctl with plugins

c8ctl supports a global plugin system that lets you add custom commands. Plugins are installed globally to a user-specific directory and tracked in a registry file (plugins.json).

Plugin storage locations​

PlatformPlugins directoryRegistry file
Linux~/.config/c8ctl/plugins/node_modules~/.config/c8ctl/plugins.json
macOS~/Library/Application Support/c8ctl/plugins/node_modules~/Library/Application Support/c8ctl/plugins.json
Windows%APPDATA%\c8ctl\plugins\node_modules%APPDATA%\c8ctl\plugins.json

You can override the data directory with the C8CTL_DATA_DIR environment variable.

Scaffold a new plugin​

Generate a new plugin project from a TypeScript template:

c8ctl init plugin my-plugin

This creates a project directory with all necessary files, build configuration, and an AGENTS.md guide for autonomous plugin implementation.

Install a plugin​

From the npm registry​

c8 load plugin my-custom-plugin

From a URL​

c8 load plugin --from https://github.com/user/my-plugin
c8 load plugin --from file:///path/to/local/plugin
c8 load plugin --from git://github.com/user/plugin.git

After loading, plugin commands are immediately available.

Manage plugins​

List installed plugins​

c8 list plugins

Output shows version and sync status for each plugin:

  • ✓ Installed — plugin is in the registry and installed.
  • ⚠ Not installed — plugin is in the registry but missing from disk (run sync).
  • ⚠ Not in registry — plugin is installed but not tracked in the registry.

Upgrade a plugin​

# Upgrade to latest
c8 upgrade plugin my-custom-plugin

# Upgrade to a specific version
c8 upgrade plugin my-custom-plugin 1.2.3

Downgrade a plugin​

c8 downgrade plugin my-custom-plugin 1.0.0

Upgrade and downgrade behavior depends on the plugin source:

SourceBehavior
npm packageInstalls <name>@<version>.
URL/gitInstalls <source>#<version>.
file://Version-based upgrade/downgrade is not supported. Use load plugin --from with the desired local checkout.

Unload a plugin​

c8 unload plugin my-custom-plugin

Synchronize plugins​

Synchronize all plugins from the registry. Rebuilds installed plugins and reinstalls any that are missing:

c8 sync plugins

Plugin structure​

A plugin is a regular Node.js module with a c8ctl-plugin.js (or c8ctl-plugin.ts) file in the root directory. The file must export a commands object and optionally a metadata object.

Minimal example​

// c8ctl-plugin.ts
export const metadata = {
name: "my-plugin",
description: "My custom c8ctl plugin",
commands: {
analyze: {
description: "Analyze BPMN processes for best practices",
},
optimize: {
description: "Optimize process definitions",
},
},
};

export const commands = {
analyze: async (args: string[]) => {
console.log("Analyzing...", args);
},
optimize: async (args: string[]) => {
console.log("Optimizing...");
},
};

Plugin runtime API​

At runtime, c8ctl injects a global object via globalThis.c8ctl that plugins can use to interact with the Camunda cluster and the c8ctl environment.

Method/fieldDescription
createClient(profile?, sdkConfig?)Create a Camunda SDK client. Optionally pass a profile name to use specific credentials.
resolveTenantId(profile?)Resolve the active tenant ID using the same fallback logic as built-in commands.
getLogger()Get the c8ctl logger instance (respects the current output mode).
versionc8ctl version string.
nodeVersionNode.js version.
platformOperating system (linux, darwin, win32).
archCPU architecture.
cwdCurrent working directory.
outputModeCurrent output mode (text or json).
activeProfileName of the active profile.
activeTenantActive tenant ID.

TypeScript autocomplete​

For TypeScript autocomplete in your plugin, import the runtime type:

import type { C8ctlPluginRuntime } from "@camunda8/cli/runtime";

const c8ctl = globalThis.c8ctl as C8ctlPluginRuntime;
const tenantId = c8ctl.resolveTenantId();
const logger = c8ctl.getLogger();
logger.info(`Tenant: ${tenantId}`);

Use the SDK client from a plugin​

import type { C8ctlPluginRuntime } from "@camunda8/cli/runtime";

const c8ctl = globalThis.c8ctl as C8ctlPluginRuntime;

export const commands = {
"list-active": async (args: string[]) => {
const client = c8ctl.createClient();
const logger = c8ctl.getLogger();
// Use the client to query the Orchestration Cluster API
logger.info("Client ready");
},
};

Help integration​

When plugins export a metadata.commands object with descriptions, those commands appear in the c8ctl help output under a Plugin Commands section:

c8ctl - Camunda 8 CLI v2.2.0

Commands:
list <resource> List resources (pi, ut, inc, jobs, profiles)
get <resource> <key> Get resource by key (pi, topology)
...

Plugin Commands:
analyze Analyze BPMN processes for best practices
optimize Optimize process definitions

Plugins without a metadata export still work — their commands appear in the help output without descriptions.

Command precedence​

Built-in commands take precedence over plugin commands. If a plugin exports a command with the same name as a built-in command (for example, list or deploy), the built-in command runs.

Use descriptive and unique names for plugin commands.

Recommended:

  • analyze-process
  • export-data
  • sync-resources

Avoid:

  • list
  • get
  • create
  • deploy

Find plugins​

Plugins are distributed as regular npm packages. There are two main ways to discover available plugins:

Search the Camunda GitHub organization​

Browse the Camunda GitHub organization and search for repositories with c8ctl in the name. By convention, plugin repositories are named c8ctl-plugin-<name> (for example, c8ctl-plugin-analyze), but this is not a hard requirement — any npm package with a c8ctl-plugin.js entry point works as a plugin.

Search the npm registry​

Search for c8ctl or c8ctl-plugin on npmjs.com:

npm search c8ctl-plugin

Once you find a plugin, install it with:

c8 load plugin <package-name>

Best practices​

  • Use unique command names to avoid conflicts with built-in commands.
  • Provide descriptions in metadata.commands so users discover your commands in c8ctl help.
  • Keep descriptions concise and aim for a single line under 60 characters, starting with an imperative verb.
  • Transpile TypeScript to JavaScript before publishing. The c8ctl-plugin.js entry point in node_modules must be JavaScript, because Node.js does not support type stripping in node_modules.
  • Use createClient() from the runtime API to create SDK clients rather than importing the SDK directly. This ensures credentials and tenant resolution follow c8ctl conventions.