Custom Plugin Guide
Custom Plugin Integration Guide
This guide covers how to register a custom executable as a managed plugin — a long-running component that the agent starts, monitors, and restarts as part of its own lifecycle. If you have not already read the Overview page, start there: it defines the plugin pattern, explains when it is the right choice over a job task, and describes the concepts this guide builds on.
If you are building a tool that runs on a schedule and exits — a scanner, reporter, or maintenance job — you want the Custom Job Integration Guide instead. This guide is specifically for processes that stay running.
The Plugin JSON File
Location and Naming
A plugin is registered by placing a JSON file at:
{AgentRoot}/Plugins/{PluginId}.jsonThe filename without .json must match the id field inside the file. A file named MyBridge.json must contain "id": "MyBridge". The plugin ID is also used as the key when calling Plugin Settings — GET /api/PluginSettings/MyBridge — so choose a stable, unique identifier and do not change it once deployed.
Binaries are typically placed under:
{AgentRoot}/Plugins/bin/{PluginId}/{PluginId}.exe (Windows)
{AgentRoot}/Plugins/bin/{PluginId}/{PluginId} (Linux / macOS)Set executablePath in the JSON to the path relative to the agent root, or to a full absolute path if your deployment layout requires it.
Anatomy of a Plugin: JSON
Here is a complete annotated example:
{
"id": "MyBridge",
"name": "My Bridge",
"description": "Bridges inbound commands to an internal service.",
"version": "1.0.0",
"pluginType": "Executable",
"executablePath": "bin/MyBridge/MyBridge.exe",
"supportedPlatforms": ["Windows"],
"Subscription": {
"Topic": "MyBridge",
"Qos": 2,
"CleanSession": true
},
"metadata": {
"mqttRole": ["subscriber", "publisher"],
"mqttTopics": {
"publish": ["KeeperLogger", "MyBridge/Responses/+"],
"subscribe": ["MyBridge/Commands/+"]
}
},
"startupPriority": 60,
"autoStart": true,
"executionContext": "Service",
"requiresMonitoring": true,
"autoRestart": true
}Field Reference
id
Yes
Stable identifier. Must match the filename. Used as the key in /api/PluginSettings/{id}.
name
No
Display name shown in logs and admin views.
description
No
Human-readable description.
version
No
Semantic version string. Used for diagnostics and change tracking.
pluginType
Yes
Executable for a standalone binary. Other types may be available depending on your agent version — confirm with your administrator.
executablePath
Yes
Path to the binary. Relative to the agent root or absolute.
arguments
No
Command-line arguments passed to the binary at launch. Supports some variable substitution — confirm supported tokens with your administrator.
supportedPlatforms
Yes
Array of "Windows", "Linux", and/or "macOS".
metadata
Yes
Container for mqttTopics and any plugin-specific keys.
metadata.mqttTopics.publish
Yes
Topics this plugin may publish to. The broker enforces this list.
metadata.mqttTopics.subscribe
Yes
Topics this plugin subscribes to beyond the primary Subscription topic.
startupPriority
No
Controls the order in which plugins start. Lower numbers start earlier. Look at the values used by other plugins in your deployment and choose a value that fits your component's dependencies.
autoStart
No
true to have the agent start the plugin automatically when the agent starts. false for on-demand or job-triggered invocation. Defaults to false if omitted.
executionContext
No
Service to run as the agent service account. Match this to your security requirements.
requiresMonitoring
No
true to have the agent watch the process and detect if it exits unexpectedly.
autoRestart
No
true to have the agent restart the plugin if monitoring detects it has stopped. Only meaningful when requiresMonitoring is also true.
enabled
No
Set to false to register the plugin without it running. Useful during development.
MQTT for Plugins
Plugins interact with the agent's MQTT broker differently from job tasks in three important ways:
They have a primary
Subscription.They declare topics under
metadata.mqttTopicsrather than on the job root.They use a different MQTT client ID format.
The Primary Subscription
Every plugin declares a primary MQTT subscription:
"Subscription": {
"Topic": "MyBridge",
"Qos": 2,
"CleanSession": true
}This is the topic the agent uses to identify and communicate with the plugin as a named component. It is separate from the additional topics declared in metadata.mqttTopics. Use Qos: 2 for commands that must not be lost or duplicated. Set CleanSession: true unless your use case requires a persistent session.
Declaring Publish and Subscribe Topics
All topics your plugin publishes to or subscribes from must be declared in metadata.mqttTopics. The broker enforces these lists — a publish to an undeclared topic will be denied even if the MQTT connection succeeds.
"metadata": {
"mqttTopics": {
"publish": ["KeeperLogger", "MyBridge/Responses/+"],
"subscribe": ["MyBridge/Commands/+"]
}
}Declare only what you actually use. Avoid broad wildcards like # unless your use case genuinely requires them — tighter topic lists reduce the blast radius of misconfiguration.
If you publish log messages to KeeperLogger, include it in publish. The message format is the same RequestMessage JSON structure described in the Custom Job Integration Guide.
MQTT Client ID Format for Plugins
Plugin MQTT client IDs use a different format from job tasks. Do not use the job triple ({JobId}_{Token}_{Pid}) for a plugin — the broker uses the client ID format to route topic permission checks, and using the wrong format will cause those checks to fail.
For a plugin running as a service account (the typical case):
{PluginName}_{ProcessId}For a plugin running in a user session:
{PluginName}_{UserName}_{ProcessId}Where {PluginName} is a short, stable token — typically aligned with your plugin id. A complete example:
import os
plugin_name = "MyBridge"
pid = os.getpid()
client_id = f"{plugin_name}_{pid}"
# Example result: "MyBridge_51204"Connecting to the Broker
The broker address is not injected as an environment variable. Your plugin fetches it from Plugin Settings at startup, using the same pattern as job tasks — call GET /api/PluginSettings/KeeperPrivilegeManager using the KeeperApiBaseUrl the agent makes available. See the code example on the Overview page.
Unlike a job task, your plugin does not receive {KeeperApiBaseUrl} as an injected argument automatically. You will need to either pass the API base URL explicitly via arguments in the plugin JSON, or fall back to the default https://127.0.0.1:6889 if your deployment guarantees that port.
The broker uses TLS on the loopback interface. Connect with your MQTT client configured for TLS, using your organization's CA bundle if provided, or disabling certificate verification for loopback-only connections per your security policy.
Plugin Settings Over HTTPS
Your plugin can read its own scoped configuration — separate from the system-wide KeeperPrivilegeManager settings — using its own plugin ID:
GET https://127.0.0.1:{httpsPort}/api/PluginSettings/MyBridgeThis returns the effective merged settings for your plugin as a flat JSON object of string key-value pairs. Settings can come from:
Your plugin JSON file (
Plugins/MyBridge.json)Unified storage (applied by policy)
System defaults
Always call this endpoint to read effective settings rather than parsing the JSON file on disk directly. The file on disk may not reflect policy overrides.
To update a single setting key from inside a running plugin:
PUT https://127.0.0.1:{httpsPort}/api/PluginSettings/MyBridge/{settingName}Both endpoints require Plugin-tier authorization — your plugin must be started by the agent to pass process authentication. A manual run of the same binary will receive 403. See the HTTP Reference for the full Plugin Settings API.
Signing and Process Trust
Binaries placed under Plugins/ paths are subject to stricter trust rules than binaries under Jobs/bin/. When the plugin orchestrator starts your binary, it registers the process in the launched-process registry, which grants MQTT and Plugin-tier HTTPS access — the same mechanism as job tasks. However, the path expectations and certificate checks that apply to plugin executables are tighter.
Sign all plugin binaries for production. Windows Authenticode is required for the standard plugin trust path on Windows. On macOS, use Apple Developer ID. On Linux, align with your deployment's signing enforcement (GPG-signed packages or IMA where applicable).
Add your code-signing certificate thumbprint to Settings:AlternativeSignatures in appsettings.json if your deployment requires explicit trust registration for the thumbprint:
{
"Settings": {
"AlternativeSignatures": [
"A1B2C3D4E5F6789012345678901234567890ABCD"
]
}
}Restart the agent service after changing appsettings.json.
Do not rely on manual runs for production validation. If you start your plugin binary manually outside the agent, it must pass a certificate check to connect to MQTT or call Plugin Settings. A binary started by the orchestrator skips that check because it is already registered. Test manual invocations in a development environment before assuming production behavior matches.
Deployment
Adding a new plugin to a production agent is a more involved process than deploying a job. It typically requires:
Packaging and signing the binary according to your organization's release process.
Placing the binary at the
executablePathspecified in the plugin JSON on every target endpoint.Dropping the plugin JSON at
{AgentRoot}/Plugins/{PluginId}.jsonusing a supported deployment mechanism — not a manual file copy if Last Known Good is enabled.Restarting the agent or triggering a plugin reload so the orchestrator picks up the new registration.
Work with your Keeper administrator to confirm the supported insertion path for new plugins in your environment. Arbitrary file drops without coordination can conflict with Last Known Good protection or packaging expectations.
Updating plugin settings after deployment does not require restarting the agent. Use PUT /api/PluginSettings/{PluginId}/{settingName} from a Plugin-tier authenticated process, or deliver a settings policy update through the console.
If settings are not taking effect, use POST /api/PluginSettings/{PluginId}/revert to re-import the plugin's on-disk JSON into unified storage, then check the effective values with a GET.
Checklist
Run through every item before rolling out to your fleet.
1
Plugin id matches filename; binary exists at executablePath on each supported platform
2
Subscription.Topic is set and unique among plugins in the deployment
3
metadata.mqttTopics.publish includes every topic the binary actually publishes to
4
metadata.mqttTopics.subscribe includes every topic the binary actually subscribes to beyond Subscription.Topic
5
MQTT client ID uses the plugin format (PluginName_Pid) — not the job triple
6
Binary is signed; thumbprint is in AlternativeSignatures if required
7
GET /api/PluginSettings/KeeperPrivilegeManager returns broker.host and broker.port from inside the running plugin
8
GET /api/PluginSettings/{PluginId} returns the expected plugin-scoped settings
9
MQTT connects, primary subscription is active, and publish to KeeperLogger produces visible log output
10
autoStart, requiresMonitoring, and autoRestart settings are validated with operations — confirm CPU usage, session behavior, and restart cadence are acceptable
11
No lifecycle conflict with a job running the same binary — separate executables for separate roles
Troubleshooting
Plugin does not start after deployment
Check executablePath resolves on the target OS; verify binary is signed; check agent logs for orchestrator errors
MQTT connection refused
Binary not registered as trusted; confirm the orchestrator started it rather than a manual run
MQTT connects but publish is denied
Topic missing from metadata.mqttTopics.publish; verify topic string matches exactly
GET /api/PluginSettings/MyBridge returns 403
Process not Plugin-authenticated; ensure the agent (not a manual invocation) started the binary
Settings changes not reflected
Unified storage may be overriding the JSON file; call /revert to re-import, then re-check
Plugin restarts unexpectedly in a loop
autoRestart: true combined with a binary that exits on a startup error; fix the root cause before re-enabling auto-restart
Duplicate processes detected
Same binary registered as both a plugin and a job task; separate the roles into distinct executables
Last updated
Was this helpful?

