OpenClaw Plugin Manifest and JSON Schema Requirements
This page explains the native OpenClaw plugin manifest (openclaw.plugin.json) and its strict JSON schema validation. Plugin developers need this to ensure their plugins are correctly configured and recognized.
Read this when
- You are building an OpenClaw plugin
- You need to ship a plugin config schema or debug plugin validation errors
This page describes the native OpenClaw plugin manifest, openclaw.plugin.json. For details on compatible bundle layouts (Agent Plugins, Codex, Claude, Cursor), refer to Plugin bundles.
Those compatible bundle formats rely on their own manifest files instead:
- Agent Plugins bundle:
plugin.jsonlocated at the package root, following the public Agent Plugins standard - Codex bundle:
.codex-plugin/plugin.json - Claude bundle:
.claude-plugin/plugin.json, or the default Claude component layout without any manifest - Cursor bundle:
.cursor-plugin/plugin.json
OpenClaw recognizes these layouts automatically but does not check them against the openclaw.plugin.json schema shown below. When a compatible bundle matches OpenClaw's runtime expectations, the system reads bundle metadata, declared skill roots, Claude command roots, Claude settings.json defaults, Claude LSP defaults, and supported hook packs.
A native OpenClaw plugin must include openclaw.plugin.json in the plugin root. OpenClaw uses it to validate configuration without running plugin code. If the manifest is missing or invalid, config validation fails and OpenClaw reports a plugin error.
The complete plugin system guide lives in Plugins, while Capability model covers the native capability model and current external-compatibility guidance.
What this file does
openclaw.plugin.json holds metadata that OpenClaw reads before your plugin code loads. Every piece of it must be inspectable cheaply, with no need to start plugin runtime.
Suitable for:
- plugin identity, config validation, and config UI hints
- auth, onboarding, and setup metadata (alias, auto-enable, provider env vars, auth choices)
- activation hints for control-plane surfaces
- root CLI command names, descriptions, and subcommand markers (
cliCommands) - shorthand model-family ownership
- static capability-ownership snapshots (
contracts) - dashboard widget data bindings and action verbs
- static MCP servers that should exist while the plugin is enabled
- QA runner metadata the shared
openclaw qahost can inspect - channel-specific config metadata merged into catalog and validation surfaces
Not suitable for: registering native runtime hooks, declaring plugin code entrypoints, or npm install metadata. Those belong in your plugin code and package.json.
Minimal example
{
"id": "voice-call",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {}
}
}
Rich example
{
"id": "openrouter",
"name": "OpenRouter",
"description": "OpenRouter provider plugin",
"version": "1.0.0",
"providers": ["openrouter"],
"modelSupport": {
"modelPrefixes": ["router-"]
},
"modelIdNormalization": {
"providers": {
"openrouter": {
"prefixWhenBare": "openrouter"
}
}
},
"providerEndpoints": [
{
"endpointClass": "openrouter",
"hostSuffixes": ["openrouter.ai"]
}
],
"providerRequest": {
"providers": {
"openrouter": {
"family": "openrouter"
}
}
},
"cliBackends": ["openrouter-cli"],
"syntheticAuthRefs": ["openrouter-cli"],
"setup": {
"providers": [
{
"id": "openrouter",
"envVars": ["OPENROUTER_API_KEY"]
}
]
},
"providerAuthAliases": {
"openrouter-coding": "openrouter"
},
"providerAuthChoices": [
{
"provider": "openrouter",
"method": "api-key",
"choiceId": "openrouter-api-key",
"choiceLabel": "OpenRouter API key",
"groupId": "openrouter",
"groupLabel": "OpenRouter",
"optionKey": "openrouterApiKey",
"cliFlag": "--openrouter-api-key",
"cliOption": "--openrouter-api-key <key>",
"cliDescription": "OpenRouter API key",
"onboardingScopes": ["text-inference"]
}
],
"uiHints": {
"apiKey": {
"label": "API key",
"placeholder": "sk-or-v1-...",
"sensitive": true
}
},
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"apiKey": {
"type": "string"
}
}
}
}
Top-level field reference
| Field | Required | Type | What it means |
|---|---|---|---|
id | Yes | string | The plugin's canonical identifier. This same id is what plugins.entries.<id> references. One exception: when a package's package.json lists several plugin entries, each entry gets registered as <id>/<entry-basename> (pack/one serves as an example), and that entry-level id becomes the plugins.entries key for that specific entry. Every entry basename must be unique across the package; duplicates are refused during discovery. |
configSchema | Yes | object | An inline JSON Schema describing the configuration for this plugin. |
requiresPlugins | No | string[] | Other plugin ids that need to be present for this plugin to function. Discovery still allows the plugin to load, but it issues a warning if any of these dependencies are absent. |
enabledByDefault | No | true | Indicates that a bundled plugin starts enabled by default. Leaving it out, or assigning a value other than true, results in the plugin being disabled initially. |
enabledByDefaultOnPlatforms | No | string[] | Restricts default enabling to the specified Node.js platforms, such as ["darwin"], for bundled plugins. Explicit user configuration always takes precedence. |
legacyPluginIds | No | string[] | Older ids that map to this canonical plugin id. |
autoEnableWhenConfiguredProviders | No | string[] | Provider ids that, when referenced in auth, config, or model settings, should trigger automatic activation of this plugin. |
kind | No | PluginKind | PluginKind[] | Specifies one or more exclusive plugin kinds ("memory", "context-engine") that plugins.slots.* relies on. A plugin occupying both slots lists both kinds within a single array. |
channels | No | string[] | Channel ids that belong to this plugin, utilized for discovery and configuration checks. |
providers | No | string[] | Provider ids that belong to this plugin. |
providerCatalogEntry | No | string | A lightweight path to the provider-catalog module, relative to the plugin's root, holding manifest-scoped catalog metadata loadable without spinning up the full plugin runtime. |
modelSupport | No | object | Manifest-level shorthand metadata for model families, used to preload the plugin before runtime execution. |
modelCatalog | No | object | Declarative model catalog metadata for the providers this plugin owns. It serves as the control-plane contract for future read-only listing, onboarding, model pickers, aliases, and suppression, all without loading the plugin runtime. |
modelPricing | No | object | The publishing policy for hosted pricing owned by the provider. It lets local or self-hosted providers exclude themselves from published pricing, or map provider refs to OpenRouter/LiteLLM catalog ids, avoiding hardcoded provider ids in core. |
modelIdNormalization | No | object | Provider-level cleanup of model-id aliases or prefixes that must execute before the provider runtime starts. |
providerEndpoints | No | object[] | Manifest-owned endpoint host or baseUrl metadata for provider routes that core must classify before the provider runtime loads. |
providerRequest | No | object | Low-cost metadata on provider families and request compatibility, consumed by generic request policy before the provider runtime loads. |
secretProviderIntegrations | No | Record<string, object> | Declarative SecretRef exec provider presets that setup or install interfaces can present without embedding provider-specific integrations in core. |
cliBackends | No | string[] | CLI inference backend ids owned by this plugin, used for startup auto-activation based on explicit config refs. |
syntheticAuthRefs | No | string[] | Provider or CLI backend refs whose plugin-owned synthetic auth hook should be tested during cold model discovery before runtime loads. |
nonSecretAuthMarkers | No | string[] | Placeholder API key values owned by bundled plugins, representing non-secret local, OAuth, or ambient credential states. |
commandAliases | No | object[] | Command names owned by this plugin that should trigger plugin-aware config and CLI diagnostics before runtime loads. |
cliCommands | No | object[] | Root CLI commands displayed in openclaw --help before plugin code executes. Each entry needs name, description, and hasSubcommands. |
providerUsageAuthEnvVars | No | Record<string, string[]> | Credentials restricted to usage and billing purposes. OpenClaw relies on these names when discovering usage and scrubbing secrets, yet they are never applied to inference authentication. |
providerAuthAliases | No | Record<string, string> | Provider identifiers that point to another provider id for authentication lookups, such as a coding provider that shares the base provider's API key and auth profiles. |
providerAuthChoices | No | object[] | Lightweight auth-choice metadata supporting onboarding pickers, preferred-provider resolution, and straightforward CLI flag configuration. |
activation | No | object | Minimal activation-planning metadata for startup, provider, command, channel, route, and capability-triggered loading. It is metadata only; the plugin runtime still governs actual behavior. |
setup | No | object | Basic setup and onboarding descriptors that discovery and setup interfaces can inspect without loading the plugin runtime. |
doctorContract | No | object | Specifies which dynamic doctor-contract surfaces the plugin artifact exports, so doctor loads only the modules it needs. |
sessionRouteStateOwners | No | object[] | Static session-route ownership for doctor cleanup. Each entry specifies an id, an label, and optionally an providerIds, runtimeIds, cliSessionKeys, and authProfilePrefixes. |
qaRunners | No | object[] | Simple QA runner descriptors consumed by the shared openclaw qa host before the plugin runtime is loaded. |
dashboard | No | object | Dashboard widget data bindings and action verbs. Each entry is checked against a Gateway method this plugin registers with the necessary read or write scope. Consult the dashboard reference. |
mcpServers | No | Record<string, object> | Static MCP server definitions contributed while this plugin is active. Relative command arguments and working directories are resolved from the plugin root. Operator mcp.servers entries override or disable definitions sharing the same name. See the MCP server reference. |
contracts | No | object | Static capability ownership snapshot for external auth hooks, embeddings, speech, realtime transcription, realtime voice, media-understanding, image/video/music generation, web fetch, web search, worker providers, document/web-content extraction, and tool ownership. |
configContracts | No | object | Manifest-owned config behavior used by generic core helpers: dangerous-flag detection, SecretRef migration targets, and legacy config-path narrowing. Refer to the configContracts reference. |
mediaUnderstandingProviderMetadata | No | Record<string, object> | Basic media-understanding defaults for provider ids listed in contracts.mediaUnderstandingProviders. |
imageGenerationProviderMetadata | No | Record<string, object> | Lightweight image-generation auth metadata for provider ids listed in contracts.imageGenerationProviders, covering provider-owned auth aliases and base-url guards. |
videoGenerationProviderMetadata | No | Record<string, object> | Lightweight video-generation auth metadata for provider ids listed in contracts.videoGenerationProviders, covering provider-owned auth aliases and base-url guards. |
musicGenerationProviderMetadata | No | Record<string, object> | Lightweight music-generation auth metadata for provider ids listed in contracts.musicGenerationProviders, covering provider-owned auth aliases and base-url guards. |
toolMetadata | No | Record<string, object> | Basic availability metadata for plugin-owned tools declared in contracts.tools. Apply it when a tool should avoid loading runtime unless config, env, or auth evidence is present. |
channelConfigs | No | Record<string, object> | Manifest-owned channel config metadata merged into discovery and validation surfaces prior to runtime loading. |
skills | No | string[] | Skill directories to load, relative to the plugin root. |
name | No | string | Human-readable plugin name. |
description | No | string | Brief summary displayed on plugin surfaces. |
catalog | No | object | Optional presentation hints for plugin catalog surfaces. This metadata does not install, enable, or grant trust to a plugin. |
icon | No | string | HTTPS image URL for marketplace/catalog cards. ClawHub accepts any valid https:// URL and falls back to the default plugin icon when this is omitted or invalid. |
version | No | string | Informational plugin version. |
uiHints | No | Record<string, object> | UI labels, placeholders, and sensitivity hints for config fields. |
For static doctor ownership, prefer top-level sessionRouteStateOwners. External plugins can still rely on the older doctorContract.sessionRouteStateOwners: true declaration paired with a sessionRouteStateOwners export from doctor-contract-api, though that approach is deprecated. When the manifest field exists, OpenClaw uses it directly and skips loading the doctor-contract module. The fallback to that module is slated for removal in OpenClaw 2027.1, following the external-plugin migration period.
Set doctorContract.configRepair: true when the doctor-contract module exports non-empty legacyConfigRules, a normalizeCompatibilityConfig function, or both. A single declaration covers the entire config-repair artifact.
MCP server reference
With mcpServers, a native plugin can bundle an MCP server, including an MCP App, so operators don't have to repeat its static process definition in openclaw.json:
{
"mcpServers": {
"example": {
"transport": "stdio",
"command": "node",
"args": ["./mcp-server.js"]
}
}
}
These servers are included by OpenClaw only while the owning plugin is active. Relative command, args, cwd, and workingDirectory paths are resolved from the plugin root. User configuration takes precedence: mcp.servers.<name> can override a plugin default, or enabled: false can be set to exclude it. MCP App rendering and server-tool calls still depend on the standard MCP Apps setting and effective tool policy; declaring a server bypasses neither boundary.
dashboard reference
dashboard allows an enabled plugin to surface existing Gateway RPCs to granted dashboard widgets without introducing plugin policy into core. Data bindings must reference a method the same plugin registers with operator.read; action verbs must reference one it registers with operator.write. Any mismatch causes the plugin to be rejected during registration.
{
"dashboard": {
"dataBindings": [
{
"id": "items.list",
"method": "example.items.list",
"description": "List example items."
}
],
"actionVerbs": [
{
"id": "refresh",
"method": "example.items.refresh",
"description": "Refresh example items.",
"paramShape": {
"type": "object",
"additionalProperties": false,
"properties": {
"force": { "type": "boolean" }
}
}
}
]
}
}
Manifest ids are scoped to the plugin. Widget grants use <plugin-id>.<id>, for instance example.items.list and example.refresh. To keep the persisted grant namespace unambiguous, OpenClaw escapes % and . in the plugin-id segment as %25 and %2E; ordinary plugin ids retain their natural form. paramShape is an optional JSON Schema applied to the action params object before OpenClaw invokes the plugin RPC.
catalog reference
catalog offers optional display hints for plugin browsers. Hosts can choose to ignore these hints. They never install or enable the plugin, nor do they alter its runtime behavior or trust level.
{
"catalog": {
"featured": true,
"order": 10
}
}
| Field | Type | What it means |
|---|---|---|
featured | boolean | Whether catalog surfaces should feature this plugin. |
order | number | Ascending display hint among curated plugins; lower values appear earlier. |
Generation provider metadata reference
The generation provider metadata fields describe static auth signals for providers declared in the matching contracts.*GenerationProviders list. OpenClaw reads these fields before provider runtime loads so core tools can decide whether a generation provider is available without importing every provider plugin.
Use these fields only for cheap, declarative facts. Transport, request transforms, token refresh, credential validation, and actual generation behavior stay in the plugin runtime.
{
"contracts": {
"imageGenerationProviders": ["example-image"]
},
"imageGenerationProviderMetadata": {
"example-image": {
"aliases": ["example-image-oauth"],
"authProviders": ["example-image"],
"configSignals": [
{
"rootPath": "plugins.entries.example-image.config",
"overlayPath": "image",
"mode": {
"path": "mode",
"default": "local",
"allowed": ["local"]
},
"requiredAny": ["workflow", "workflowPath"],
"required": ["promptNodeId"]
}
],
"authSignals": [
{
"provider": "example-image"
},
{
"provider": "example-image-oauth",
"providerBaseUrl": {
"provider": "example-image",
"defaultBaseUrl": "https://api.example.com/v1",
"allowedBaseUrls": ["https://api.example.com/v1"]
}
}
]
}
}
}
Each metadata entry supports:
| Field | Required | Type | What it means |
|---|---|---|---|
aliases | No | string[] | Extra provider identifiers that act as static auth aliases for the generation provider. |
authProviders | No | string[] | Provider identifiers whose configured auth profiles are treated as auth for this generation provider. |
configSignals | No | object[] | Lightweight, config-based availability checks for local or self-hosted providers that work without auth profiles or environment variables. |
authSignals | No | object[] | Direct auth indicators. When set, they override the default indicator set derived from the provider id, aliases, and authProviders. |
referenceAudioInputs | No | boolean | Only for video generation. Use true when the provider handles reference audio files; otherwise video_generate removes audio reference parameters. |
Each configSignals entry supports:
| Field | Required | Type | What it means |
|---|---|---|---|
rootPath | Yes | string | Dot-separated path to the plugin-owned config object to examine, such as plugins.entries.example.config. |
overlayPath | No | string | Dot-separated path inside the root config whose object overlays the root object before the signal is evaluated. Useful for capability-specific configs like image, video, or music. |
overlayMapPath | No | string | Dot-separated path inside the root config whose object values each overlay the root object. For named account maps such as accounts, any configured account qualifies. |
required | No | string[] | Dot-separated paths in the effective config that must have values set. Strings need to be non-empty; objects and arrays cannot be empty. |
requiredAny | No | string[] | Dot-separated paths in the effective config where at least one must have a value set. |
mode | No | object | Optional string mode guard inside the effective config. Apply this when config-only availability applies to a single mode. |
Each mode guard supports:
| Field | Required | Type | What it means |
|---|---|---|---|
path | No | string | Dot-separated path in the effective config. Falls back to mode. |
default | No | string | Mode value used when the config does not include the path. |
allowed | No | string[] | When present, the signal passes only if the effective mode matches one of these values. |
disallowed | No | string[] | When present, the signal fails if the effective mode matches one of these values. |
Each authSignals entry supports:
| Field | Required | Type | What it means |
|---|---|---|---|
provider | Yes | string | Provider id to look up in the configured auth profiles. |
providerBaseUrl | No | object | Optional guard that makes the signal count only when the referenced configured provider uses an allowed base URL. Use this when an auth alias works only with certain APIs. |
Each providerBaseUrl guard supports:
| Field | Required | Type | What it means |
|---|---|---|---|
provider | Yes | string | Provider config id whose baseUrl is examined. |
defaultBaseUrl | No | string | Base URL assumed when the provider config does not include baseUrl. |
allowedBaseUrls | Yes | string[] | Allowed base URLs for this auth signal. The signal is ignored when the configured or default base URL does not match one of these normalized values. |
Tool metadata reference
toolMetadata relies on the identical configSignals and authSignals structures found in generation provider metadata, with the tool name acting as the key. Ownership is established through contracts.tools. By declaring cheap availability evidence via toolMetadata, OpenClaw can skip loading a plugin runtime when its tool factory would only return null.
{
"setup": {
"providers": [
{
"id": "example",
"envVars": ["EXAMPLE_API_KEY"]
}
]
},
"contracts": {
"tools": ["example_search"]
},
"toolMetadata": {
"example_search": {
"profiles": ["coding", "full"],
"authSignals": [
{
"provider": "example"
}
],
"configSignals": [
{
"rootPath": "plugins.entries.example.config",
"overlayPath": "search",
"required": ["apiKey"]
}
]
}
}
}
Additional accepted fields for toolMetadata entries are:
profiles: predefined tool profiles that include the plugin tool by default. The valid options areminimal,coding,messaging, andfull. These contributions are merged into the corresponding profile allowlist, while explicit operator allowlists and deny rules keep their authority.optional: indicates the tool is not essential for plugin activation.replaySafe: designates tool execution as safe for repetition following an incomplete model turn.sideEffecting: flags execution as potentially altering persistent or external state.
These extras complement the shared configSignals and authSignals fields mentioned earlier.
When a tool lacks a toolMetadata, OpenClaw retains its current behavior, loading the owning plugin if the tool contract matches policy. For hot-path tools whose factory depends on auth or config, plugin authors should opt for toolMetadata rather than forcing the core import runtime to make the request.
providerAuthChoices reference
Every providerAuthChoices entry covers a single onboarding or authentication option. OpenClaw reviews this before the provider runtime loads. Provider setup lists draw on these manifest choices, descriptor-derived setup options, and install-catalog metadata without ever loading the provider runtime.
| Field | Required | Type | What it means |
|---|---|---|---|
provider | Yes | string | The provider this choice is associated with. |
method | Yes | string | Which auth method id the dispatch should target. |
choiceId | Yes | string | A stable id for the auth choice, referenced by onboarding and CLI flows. |
choiceLabel | No | string | Label shown to users. When absent, OpenClaw substitutes choiceId. |
choiceHint | No | string | Brief explanatory text for the picker. |
icon | No | HTTPS URL | Visual asset displayed next to this option in onboarding clients that support it. |
website | No | HTTPS URL | Page for the product, sign-in, or installation that supporting onboarding clients open. |
assistantPriority | No | number | Smaller numbers position the option earlier in assistant-driven interactive pickers. |
assistantVisibility | No | "visible" | "manual-only" | Keep the option out of assistant pickers, though manual CLI selection remains possible. |
deprecatedChoiceIds | No | string[] | Older choice ids that should point users to this replacement choice. |
groupId | No | string | Optional group identifier for clustering related options. |
groupLabel | No | string | Display name for that group. |
groupHint | No | string | Concise helper text for the group. |
onboardingFeatured | No | boolean | Show the group in the featured section of the interactive onboarding picker, ahead of the "More..." item. |
optionKey | No | string | Internal option key meant for simple one-flag auth flows. |
cliFlag | No | string | CLI flag name, for instance --openrouter-api-key. |
cliOption | No | string | Complete CLI option structure, for instance --openrouter-api-key <key>. |
cliDescription | No | string | Text shown in CLI help. |
appGuidedSecret | No | boolean | One pasted secret plus provider defaults suffices for app-guided setup. |
appGuidedActionLabel | No | string | Short command label used when provider-owned app-guided setup begins. |
appGuidedDiscovery | No | boolean | The corresponding runtime auth method handles read-only local discovery via appGuidedSetup. |
appGuidedAuth | No | "oauth" | "device-code" | Provider-owned interactive login that native setup clients can render generically. |
onboardingScopes | No | Array<"text-inference" | "image-generation" | "music-generation"> | Where this choice should appear across onboarding surfaces. When unset, ["text-inference"] is used. |
When appGuidedDiscovery is true, the associated provider auth method must provide
appGuidedSetup.detect and appGuidedSetup.prepare. Detection operates
read-only, meaning no login, model pull, download, or config write occurs. Preparation
revalidates the exact chosen model and yields a config proposal; OpenClaw tests that
proposal in isolation and commits it only after success. A provider may also
expose appGuidedSetup.detectAvailability to flag its setup choice as detected
when the local service is reachable but no model fits automatic setup.
The availability probe also stays read-only.
cliCommands reference
List every plugin-owned root command in cliCommands so root help and command-owner routing remain metadata-only:
{
"cliCommands": [
{
"name": "example",
"description": "Manage the example integration",
"hasSubcommands": true
}
]
}
The manifest row serves as the authoritative help text. At runtime, register the same command using api.registerCli(..., { descriptors: [...] }); runtime descriptors can also supply machineOutput. Commands nested like openclaw nodes <feature> are not root commands and should not appear in cliCommands.
commandAliases reference
When a plugin owns a runtime command name that users might place in plugins.allow or attempt to invoke as a root CLI command, use commandAliases. OpenClaw relies on this metadata for diagnostics without loading the plugin's runtime code.
{
"commandAliases": [
{
"name": "dreaming",
"kind": "runtime-slash",
"cliCommand": "memory"
}
]
}
| Field | Required | Type | What it means |
|---|---|---|---|
name | Yes | string | Command name assigned to this plugin. |
kind | No | "runtime-slash" | Indicates the alias is a chat slash command, not a root CLI command. |
cliCommand | No | string | Root CLI command to recommend for CLI usage, when applicable. |
activation reference
Employ activation when the plugin can inexpensively specify which control-plane events should place it in an activation or load plan.
This block is planner metadata, not a lifecycle API. It neither registers runtime behavior, nor replaces register(...), nor guarantees that plugin code has run. The activation planner consults these fields to reduce candidate plugins before relying on existing manifest ownership metadata, such as providers, channels, commandAliases, setup.providers, contracts.tools, and hooks.
Choose the most specific metadata that already captures ownership. Use providers, channels, commandAliases, setup descriptors, or contracts when those fields convey the relationship. For extra planner hints that those ownership fields cannot express, use activation. For CLI runtime aliases like claude-cli, my-cli, or google-gemini-cli, use top-level cliBackends; activation.onAgentHarnesses applies only to embedded agent harness ids lacking an ownership field.
Every plugin should set activation.onStartup deliberately. Assign true only when the plugin must run during Gateway startup. Assign false when the plugin is idle at startup and should load solely from narrower triggers. Omitting onStartup no longer causes implicit startup loading; instead, rely on explicit activation metadata for startup, channel, config, agent-harness, memory, or other narrower activation triggers.
{
"activation": {
"onStartup": false,
"onProviders": ["openai"],
"onCommands": ["models"],
"onChannels": ["web"],
"onRoutes": ["gateway-webhook"],
"onConfigPaths": ["browser"],
"onCapabilities": ["provider", "tool"]
}
}
| Field | Required | Type | What it means |
|---|---|---|---|
onStartup | No | boolean | Explicit Gateway startup activation. Every plugin should set this. true imports the plugin during startup; false keeps it startup-lazy unless another matched trigger requires loading. |
onProviders | No | string[] | Provider ids that should include this plugin in activation/load plans. |
onAgentHarnesses | No | string[] | Embedded agent harness runtime ids that should include this plugin in activation/load plans. Use top-level cliBackends for CLI backend aliases. |
onCommands | No | string[] | Command ids that should include this plugin in activation/load plans. |
onChannels | No | string[] | Channel ids that should include this plugin in activation/load plans. |
onRoutes | No | string[] | Route kinds that should include this plugin in activation/load plans. |
onConfigPaths | No | string[] | Root-relative config paths that should include this plugin in startup/load plans when the path is present and not explicitly disabled. |
onCapabilities | No | Array<"provider" | "channel" | "tool" | "hook"> | Broad capability hints used by control-plane activation planning. Prefer narrower fields when possible. |
Current live consumers:
- During gateway startup,
activation.onStartupis referenced for explicit import handling. - CLI planning invoked by commands relies on the older
commandAliases[].cliCommandorcommandAliases[].namemechanisms. - For agent-runtime initialization,
activation.onAgentHarnessesapplies to embedded harnesses, whilecliBackends[]covers top-level CLI runtime aliases. - When explicit channel activation metadata is absent, channel-triggered setup and planning revert to legacy
channels[]ownership. - Non-channel root configuration surfaces, like the bundled browser plugin's
browsersection, useactivation.onConfigPathsfor startup plugin planning. - Provider-triggered setup and runtime planning fall back to legacy
providers[]and top-levelcliBackends[]ownership when explicit provider activation metadata is unavailable.
Diagnostics from the planner can tell apart explicit activation signals from manifest ownership fallbacks. For instance, activation-command-hint signals that activation.onCommands was a match, whereas manifest-command-alias indicates the planner relied on commandAliases ownership instead. These reason tags serve host diagnostics and tests; plugin authors should continue declaring the metadata that best reflects ownership.
qaRunners reference
When a plugin provides one or more transport runners under the shared openclaw qa root, use qaRunners. Keep this metadata lightweight and static; actual CLI registration remains with the plugin runtime via a minimal qa-runner-api.ts surface that exports corresponding qaRunnerCliRegistrations. For plugins adopting the shipped runtime-api.ts contract, that legacy surface stays accepted until 2026-10-01 while migration proceeds. An optional adapterFactory makes the transport available for shared QA scenarios without altering the registered command's runner.
Module-backed flow scenarios represent an adapter-owned execution mode. Assign adapterFactory.supportsModuleFlows to true only when every adapter produced by that factory implements prepareFlow; QA planning omits module flows from implementations that do not declare support.
{
"qaRunners": [
{
"commandName": "matrix",
"description": "Run the Docker-backed Matrix live QA lane against a disposable homeserver"
}
]
}
| Field | Required | Type | What it means |
|---|---|---|---|
commandName | Yes | string | Subcommand placed under openclaw qa, such as matrix. |
description | No | string | Fallback help text when the shared host requires a stub command. |
The adapterFactory id must align with commandName. Do not export registrations for commands that are missing from the manifest.
setup reference
Use setup when setup and onboarding surfaces need inexpensive plugin-owned metadata before runtime loads.
{
"setup": {
"providers": [
{
"id": "openai",
"authMethods": ["api-key"],
"envVars": ["OPENAI_API_KEY"],
"authEvidence": [
{
"type": "local-file-with-env",
"fileEnvVar": "OPENAI_CREDENTIALS_FILE",
"requiresAllEnv": ["OPENAI_PROJECT"],
"credentialMarker": "openai-local-credentials",
"source": "openai local credentials"
}
]
}
],
"cliBackends": ["openai-cli"],
"configMigrations": ["legacy-openai-auth"],
"requiresRuntime": false
}
}
Top-level cliBackends remains valid and continues to describe CLI inference backends. setup.cliBackends serves as the setup-specific descriptor surface for control-plane/setup flows that should stay metadata-only.
When present, setup.providers and setup.cliBackends become the preferred descriptor-first lookup surface for setup discovery. If the descriptor only narrows the candidate plugin and setup still requires richer setup-time runtime hooks, set requiresRuntime: true and retain setup-api as the fallback execution path.
OpenClaw includes setup.providers[].envVars in generic provider auth and env-var lookups. Place setup and status env metadata there.
Use providerUsageAuthEnvVars when a billing or organization-level credential must activate resolveUsageAuth without becoming an inference credential. These names join workspace dotenv blocking, ACP child-process stripping, sandbox secret filtering, and broad secret scrubbing. The provider runtime still reads and classifies the value inside resolveUsageAuth.
OpenClaw can also derive simple setup choices from setup.providers[].authMethods when no setup entry is available, or when setup.requiresRuntime: false declares setup runtime unnecessary. Explicit providerAuthChoices entries remain preferred for custom labels, CLI flags, onboarding scope, and assistant metadata.
Set requiresRuntime: false only when those descriptors are sufficient for the setup surface. OpenClaw treats explicit false as a descriptor-only contract and will not execute setup-api or openclaw.setupEntry for setup lookup. If a descriptor-only plugin still ships one of those setup runtime entries, OpenClaw reports an additive diagnostic and continues ignoring it. Omitted requiresRuntime keeps legacy fallback behavior so existing plugins that added descriptors without the flag do not break.
Because setup lookup can execute plugin-owned setup-api code, normalized setup.providers[].id and setup.cliBackends[] values must stay unique across discovered plugins. Ambiguous ownership fails closed instead of picking a winner from discovery order.
When setup runtime executes, setup registry diagnostics report providers or CLI backends that setup-api registers without matching manifest declarations. CLI backend descriptors also report a missing runtime registration because setup lookup needs the registered backend configuration. Provider descriptors may remain metadata-only even when the same setup module contributes migrations, CLI backends, probes, or selected provider runtimes.
setup.providers reference
| Field | Required | Type | What it means |
|---|---|---|---|
id | Yes | string | The provider id shown during setup or onboarding. Normalized ids must stay unique across the globe. |
authMethods | No | string[] | Setup or auth method ids this provider supports without needing the full runtime loaded. |
envVars | No | string[] | Environment variables that generic setup or status screens can inspect before the plugin runtime is loaded. |
authEvidence | No | object[] | Low-cost local auth checks for providers that can authenticate via non-secret indicators. |
authEvidence handles provider-specific local credential markers that can be verified without loading runtime code. These checks must remain inexpensive and local: no network requests, no keychain or secret-manager access, no shell commands, and no provider API calls.
Supported evidence entries:
| Field | Required | Type | What it means |
|---|---|---|---|
type | Yes | string | Currently set to local-file-with-env. |
fileEnvVar | No | string | Environment variable holding an explicit credential file path. |
fallbackPaths | No | string[] | Local credential file paths checked when fileEnvVar is missing or empty. Supports ${HOME} and ${APPDATA}. |
requiresAnyEnv | No | string[] | At least one listed environment variable must be non-empty for the evidence to be valid. |
requiresAllEnv | No | string[] | Every listed environment variable must be non-empty for the evidence to be valid. |
credentialMarker | Yes | string | Non-secret marker returned when the evidence is present. |
source | No | string | User-facing source label for auth or status output. |
setup fields
| Field | Required | Type | What it means |
|---|---|---|---|
providers | No | object[] | Provider setup descriptors exposed during setup and onboarding. |
cliBackends | No | string[] | Setup-time backend ids used for descriptor-first setup lookup. Normalized ids must stay unique globally. |
configMigrations | No | string[] | Config migration ids owned by this plugin's setup surface. |
requiresRuntime | No | boolean | Whether setup still requires setup-api execution after descriptor lookup. Explicit false disables it; omitting it keeps the legacy fallback. |
uiHints reference
uiHints maps config field names to small rendering hints. Keys may use dots for nested config fields, but no path segment can be __proto__, constructor, or prototype; setup rejects those names.
{
"uiHints": {
"apiKey": {
"label": "API key",
"help": "Used for OpenRouter requests",
"placeholder": "sk-or-v1-...",
"sensitive": true
}
}
}
Each field hint can include:
| Field | Type | What it means |
|---|---|---|
label | string | User-facing field label. |
help | string | Brief helper text. |
tags | string[] | Optional UI tags. |
advanced | boolean | Flags the field as advanced. |
sensitive | boolean | Flags the field as secret or sensitive. |
placeholder | string | Placeholder text for form inputs. |
presentation | "phone-number" | Display-only localized phone formatting for parseable international (+...) values; raw values stay unchanged. |
Channel configuration sections pick up help for the leaf settings that every channel has in common (enabled, allowFrom, dmPolicy, groupPolicy, streaming, and the like), both at the channel root and beneath accounts.<id>. If a channel defines its own help for any of those keys, that definition takes precedence, so supply an override whenever the default wording doesn't fit your provider. Credentials, hosts, webhooks, and other provider-specific keys continue to require their own hints.
contracts reference
Reserve contracts strictly for static capability ownership metadata that OpenClaw can access without loading the plugin runtime.
{
"contracts": {
"agentToolResultMiddleware": ["openclaw", "codex"],
"trustedToolPolicies": ["workflow-budget"],
"externalAuthProviders": ["acme-ai"],
"embeddingProviders": ["openai-compatible"],
"speechProviders": ["openai"],
"realtimeTranscriptionProviders": ["openai"],
"realtimeVoiceProviders": ["openai"],
"mediaUnderstandingProviders": ["openai"],
"imageGenerationProviders": ["openai"],
"videoGenerationProviders": ["qwen"],
"musicGenerationProviders": ["stability-audio"],
"documentExtractors": ["example-docs"],
"webContentExtractors": ["firecrawl"],
"webFetchProviders": ["firecrawl"],
"webSearchProviders": ["gemini"],
"workerProviders": ["example-worker"],
"usageProviders": ["acme-ai"],
"migrationProviders": ["hermes"],
"gatewayMethodDispatch": ["authenticated-request"],
"tools": ["firecrawl_search", "firecrawl_scrape"]
}
}
Every list is optional. For speechProviders and realtimeVoiceProviders, put the canonical provider ID first, then any aliases tied to that capability:
| Field | Type | What it means |
|---|---|---|
embeddedExtensionFactories | string[] | Codex app-server extension factory ids, currently codex-app-server. |
agentToolResultMiddleware | string[] | Runtime ids this plugin may register tool-result middleware for. |
trustedToolPolicies | string[] | Plugin-local trusted pre-tool policy ids an installed plugin may register. Bundled plugins may register policies without this field. |
externalAuthProviders | string[] | Provider ids whose external auth profile hook this plugin owns. |
embeddingProviders | string[] | General embedding provider ids this plugin owns for reusable vector embedding use, including memory. |
speechProviders | string[] | Speech provider ids this plugin owns. |
realtimeTranscriptionProviders | string[] | Realtime-transcription provider ids this plugin owns. |
realtimeVoiceProviders | string[] | Realtime-voice provider ids this plugin owns. |
mediaUnderstandingProviders | string[] | Media-understanding provider ids this plugin owns. |
transcriptSourceProviders | string[] | Transcript source provider ids this plugin owns. |
documentExtractors | string[] | Document (for example PDF) extractor provider ids this plugin owns. |
imageGenerationProviders | string[] | Image-generation provider ids this plugin owns. |
videoGenerationProviders | string[] | Video-generation provider ids this plugin owns. |
musicGenerationProviders | string[] | Music-generation provider ids this plugin owns. |
webContentExtractors | string[] | Web-page content-extraction provider ids this plugin owns. |
webFetchProviders | string[] | Web-fetch provider ids this plugin owns. |
webSearchProviders | string[] | Web-search provider ids this plugin owns. |
workerProviders | string[] | Cloud-worker provider ids this plugin owns for provisioning and profile-backed lease lifecycle. |
usageProviders | string[] | Provider ids whose usage-auth and usage-snapshot hooks this plugin owns. |
migrationProviders | string[] | Import provider ids this plugin owns for openclaw migrate. |
gatewayMethodDispatch | string[] | Reserved entitlement for authenticated plugin HTTP routes that dispatch Gateway methods in-process. |
tools | string[] | Agent tool names this plugin owns. |
contracts.embeddedExtensionFactories remains in place for bundled Codex app-server-only extension factories. Tool-result transforms that ship bundled should instead declare contracts.agentToolResultMiddleware and register through api.registerAgentToolResultMiddleware(...). Installed plugins can tap the same middleware seam only when explicitly enabled and solely for runtimes they list in contracts.agentToolResultMiddleware.
For installed plugins requiring the host-trusted pre-tool policy tier, each registered local id must be declared in contracts.trustedToolPolicies and explicitly enabled. Bundled plugins continue using the existing trusted-policy path, yet installed plugins with undeclared policy ids face rejection prior to registration. Policy ids are scoped to the registering plugin, so two plugins can both declare and register workflow-budget; a single plugin cannot register the same local id twice.
Runtime api.registerTool(...) registrations have to align with contracts.tools. Tool discovery consults this list to load only the plugin runtimes capable of owning the requested tools.
Provider plugins implementing resolveExternalAuthProfiles should declare contracts.externalAuthProviders; external-auth hooks left undeclared are ignored.
Provider plugins implementing both resolveUsageAuth and fetchUsageSnapshot should declare each auto-discovered provider id in contracts.usageProviders. Usage discovery reads this contract before runtime code loads, then checks both hooks after loading only the declared owners.
Embedding providers must declare contracts.embeddingProviders for each adapter registered with api.registerEmbeddingProvider(...). The same generic contract serves reusable vector generation and memory search. The retired contracts.memoryEmbeddingProviders key is no longer accepted.
Worker providers must declare each api.registerWorkerProvider(...) id in contracts.workerProviders. Core persists durable intent before calling provision; providers validate their settings and any optional per-dispatch machineClass before external allocation, and repeated calls with the same operation id must adopt the same lease. Providers may implement asynchronous listMachineOptions(profile) to expose process-stable picker metadata; omit it when machine selection is not meaningful. Machine options contain only id, label, optional positive-integer cpu and memoryGb, and optional default. Providers used for session placement must declare exactly one supportedExecutionModes value: worker-turn providers return node leases and remote-exec providers return SSH leases. Omission advertises no session-placement modes while leaving direct lifecycle operations available. Providers whose bounded provisioning exceeds core's five-minute default may implement resolveProvisionTimeoutMs(profile) and include acquisition, provider-owned setup, and cleanup in the returned positive millisecond budget. Core also persists that validated settings snapshot and passes it with leaseId to inspect({ leaseId, profile }) and destroy({ leaseId, profile }), including after the named profile is changed or removed. Destruction is idempotent, inspection returns the closed active / destroyed / unknown status union, and SSH private-key material is referenced only through SecretRef. Provisioned SSH endpoints must also include a public hostKey from trusted provisioning output as exactly algorithm base64, without a hostname or comment, so core can pin the host before connecting. They may include up to 10 ordered, unique fallbackPorts, excluding the primary port; core persists those candidates and rotates among them only for idempotent probes, content-addressed transfers, receipt/lock-guarded artifact installation, convergent managed-worktree mirroring, and tunnel reconnects. Ambiguous unguarded stateful commands fail closed and are not replayed across candidates. A lease may set sharedHost: true when the SSH account also owns unrelated processes; core then avoids host-wide process freezing during workspace reconciliation. Omitted or false means a dedicated worker host. Active inspection repeats this fact so core can reconcile provider-owned isolation for leases persisted before the field existed; tunnel startup waits for that first authoritative inspection. Optional desktop metadata may advertise up to eight unique closed apps: browser with an absolute executablePath and a CDP port from 1 through 65535, or terminal with an absolute executablePath. Core rejects unknown app ids and fields and persists the validated metadata with the existing desktop record. Providers that mint dynamic identity refs may implement authoritative resolveSshIdentity({ leaseId, profile, keyRef }); providers without it use core's generic secret resolver. An authoritative unknown orphans an active local record; after a persisted destroy request it confirms teardown.
contracts.gatewayMethodDispatch currently accepts "authenticated-request". It is an API hygiene gate for native plugin HTTP routes that intentionally dispatch Gateway control-plane methods in-process, not a sandbox against malicious native plugins. Use it only for tightly reviewed bundled/operator surfaces that already require Gateway HTTP auth. An entitled route remains reachable while Gateway root-work admission is closed only when it also declares auth: "gateway" and the route-specific gatewayRuntimeScopeSurface: "trusted-operator"; ordinary sibling routes from the same plugin remain behind the admission boundary. This keeps suspension status and resume reachable without granting the whole plugin an admission bypass. Keep parsing and response shaping bounded outside dispatch; substantive or mutating work must go through Gateway method dispatch, which owns admission and scope enforcement.
configContracts reference
Use configContracts for manifest-owned config behavior that generic core helpers need without importing plugin runtime: dangerous-flag detection, SecretRef migration targets, and legacy config-path narrowing.
{
"configContracts": {
"compatibilityMigrationPaths": ["legacyProvider"],
"compatibilityRuntimePaths": ["legacyProvider.webhook"],
"dangerousFlags": [
{
"path": "accounts.*.allowUnverifiedSenders",
"equals": true
}
],
"secretInputs": {
"bundledDefaultEnabled": false,
"paths": [
{
"path": "routes.*.secret",
"expected": "string",
"ownerKind": "route"
}
]
}
}
}
| Field | Required | Type | What it means |
|---|---|---|---|
compatibilityMigrationPaths | No | string[] | Config paths relative to the root that signal this plugin may have setup-time compatibility migrations. Generic runtime config reads can bypass all plugin setup surfaces when the config never mentions the plugin. |
compatibilityRuntimePaths | No | string[] | Root-relative compatibility paths the plugin can handle at runtime before its code is fully active. Use these for legacy surfaces that should limit bundled candidate sets without loading every compatible plugin runtime. |
dangerousFlags | No | object[] | Config literals that openclaw doctor should mark as insecure or risky when turned on. Details follow. |
secretInputs | No | object | Config paths located under plugins.entries.<id>.config for SecretRef migration, auditing, startup materialization, and optional runtime owner isolation. Details follow. |
Each dangerousFlags entry supports:
| Field | Required | Type | What it means |
|---|---|---|---|
path | Yes | string | Dot-separated config path relative to plugins.entries.<id>.config. Supports * wildcards for map/array segments. |
equals | Yes | string | number | boolean | null | Exact literal that flags this config value as dangerous. |
secretInputs supports:
| Field | Required | Type | What it means |
|---|---|---|---|
bundledDefaultEnabled | No | boolean | Override bundled-plugin default enablement when deciding whether this SecretRef surface is active. Use this when the plugin is bundled but the surface should stay inactive until explicitly enabled in config. |
paths | Yes | object[] | Secret-shaped config paths, each with path (dot-separated, relative to plugins.entries.<id>.config, supports * wildcards), optional expected (currently only "string"), and optional ownerKind (currently only "route"). A declared owner isolates only that exact matched path when resolution fails; its owner id is the full config path. |
mediaUnderstandingProviderMetadata reference
Employ mediaUnderstandingProviderMetadata when a media-understanding provider has default models, auto-auth fallback priority, or native document support that generic core helpers need before runtime loads. Keys must also be declared in contracts.mediaUnderstandingProviders.
{
"contracts": {
"mediaUnderstandingProviders": ["example"]
},
"mediaUnderstandingProviderMetadata": {
"example": {
"capabilities": ["image", "audio"],
"defaultModels": {
"image": "example-vision-latest",
"audio": "example-transcribe-latest"
},
"autoPriority": {
"image": 40
},
"nativeDocumentInputs": ["pdf"],
"documentModels": {
"pdf": {
"textExtraction": "example-doc-text-latest",
"image": "example-doc-vision-latest"
}
}
}
}
}
Each provider entry can include:
| Field | Type | What it means |
|---|---|---|
capabilities | ("image" | "audio" | "video")[] | Media capabilities exposed by this provider. |
defaultModels | Record<string, string> | Capability-to-model defaults used when config does not specify a model. |
autoPriority | Record<string, number> | Lower numbers sort earlier for automatic credential-based provider fallback. |
nativeDocumentInputs | "pdf"[] | Native document inputs supported by the provider. |
documentModels | { pdf?: { textExtraction?: string; image?: string | false } } | Per-document-type model overrides. Set image: false to disable image-based extraction for that document type. |
channelConfigs reference
Apply channelConfigs when a channel plugin needs cheap config metadata before runtime loads. Read-only channel setup/status discovery can use this metadata directly for configured external channels when no setup entry is available, or when setup.requiresRuntime: false declares setup runtime unnecessary.
channelConfigs is plugin manifest metadata, not a new top-level user config section. Users still configure channel instances under channels.<channel-id>. OpenClaw reads manifest metadata to decide which plugin owns that configured channel before plugin runtime code executes.
For a channel plugin, configSchema and channelConfigs describe different paths:
configSchemavalidatesplugins.entries.<plugin-id>.configchannelConfigs.<channel-id>.schemavalidateschannels.<channel-id>
Non-bundled plugins that declare channels[] should also declare matching channelConfigs entries. Without them, OpenClaw can still load the plugin, but cold-path config schema, setup, and Control UI surfaces cannot know the channel-owned option shape or display-only UI hints until plugin runtime executes.
channelConfigs.<channel-id>.commands.nativeCommandsAutoEnabled and nativeSkillsAutoEnabled are able to set static auto defaults for command configuration validation that occurs prior to channel runtime initialization. Channels that ship with the package can also expose these same defaults via package.json#openclaw.channel.commands alongside their other channel catalog metadata owned by the package.
{
"channelConfigs": {
"matrix": {
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"homeserverUrl": { "type": "string" }
}
},
"uiHints": {
"homeserverUrl": {
"label": "Homeserver URL",
"placeholder": "https://matrix.example.com"
}
},
"label": "Matrix",
"description": "Matrix homeserver connection",
"commands": {
"nativeCommandsAutoEnabled": true,
"nativeSkillsAutoEnabled": true
},
"preferOver": ["matrix-legacy"]
}
}
}
Each channel entry has the following optional fields:
| Field | Type | What it means |
|---|---|---|
schema | object | JSON Schema for channels.<id>. Every declared channel config entry must include this. |
uiHints | Record<string, object> | Optional labels, placeholders, sensitivity, and display-only presentation hints for that channel config section. |
label | string | Channel label merged into picker and inspect surfaces when runtime metadata is not ready. |
description | string | Short channel description for inspect and catalog surfaces. |
commands | object | Static native command and native skill auto-defaults for pre-runtime config checks. |
preferOver | string[] | Legacy or lower-priority plugin ids this channel should outrank in selection surfaces. |
Replacing another channel plugin
Reach for preferOver when your plugin should be the designated owner for a channel id that another plugin is also capable of supplying. Typical scenarios include a renamed plugin id, a standalone plugin replacing a bundled one, or a maintained fork that preserves the same channel id for configuration compatibility.
{
"id": "acme-chat",
"channels": ["chat"],
"channelConfigs": {
"chat": {
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"webhookUrl": { "type": "string" }
}
},
"preferOver": ["chat"]
}
}
}
When channels.chat is set, OpenClaw evaluates both the channel id and the preferred plugin id. If the lower-priority plugin was chosen only due to being bundled or enabled by default, OpenClaw removes it from the effective runtime config, leaving a single plugin in charge of the channel and its tools. Explicit user selection takes precedence: when the user deliberately enables both plugins (through plugins.allow or a concrete plugins.entries config), OpenClaw keeps that selection intact and flags duplicate channel/tool diagnostics instead of quietly altering the requested plugin set.
Restrict preferOver to plugin ids that genuinely offer the same channel. It is not a catch-all priority field, nor does it rename user configuration keys.
modelSupport reference
Employ modelSupport when OpenClaw should deduce your provider plugin from shorthand model identifiers such as gpt-5.6-sol or claude-sonnet-4.6 before the plugin runtime loads.
{
"modelSupport": {
"modelPrefixes": ["gpt-", "o1", "o3", "o4"],
"modelPatterns": ["^computer-use-preview"]
}
}
OpenClaw applies the following order of precedence:
- explicit
provider/modelrefs rely on the owningprovidersmanifest metadata modelPatternstake priority overmodelPrefixes- when one non-bundled plugin and one bundled plugin both match, the non-bundled plugin is chosen
- any remaining ambiguity is left unresolved until the user or config designates a provider
Fields:
| Field | Type | What it means |
|---|---|---|
modelPrefixes | string[] | Prefixes matched with startsWith against shorthand model ids. |
modelPatterns | string[] | Regex sources matched against shorthand model ids after profile suffix removal. |
modelPatterns entries are processed through compileSafeRegex, which rejects patterns with nested repetition, such as (a+)+$. Patterns failing this safety check are silently omitted, just like regex that is syntactically invalid. Keep patterns straightforward and steer clear of nested quantifiers.
modelCatalog reference
Use modelCatalog when OpenClaw needs provider model metadata ahead of plugin runtime loading. This serves as the manifest-owned source for fixed catalog rows, provider aliases, suppression rules, and discovery mode. Runtime refresh still lives in provider runtime code, but the manifest signals to core when runtime becomes necessary.
{
"providers": ["openai"],
"modelCatalog": {
"providers": {
"openai": {
"baseUrl": "https://api.openai.com/v1",
"api": "openai-responses",
"models": [
{
"id": "gpt-5.4",
"name": "GPT-5.4",
"input": ["text", "image"],
"reasoning": true,
"contextWindow": 256000,
"maxTokens": 128000,
"cost": {
"input": 1.25,
"output": 10,
"cacheRead": 0.125
},
"status": "available",
"tags": ["default"]
}
]
}
},
"aliases": {
"azure-openai-responses": {
"provider": "openai",
"api": "azure-openai-responses"
}
},
"suppressions": [
{
"provider": "azure-openai-responses",
"model": "gpt-5.3-codex-spark",
"reason": "not available on Azure OpenAI Responses"
}
],
"discovery": {
"openai": "static"
}
}
}
Top-level fields:
| Field | Type | What it means |
|---|---|---|
providers | Record<string, object> | Catalog rows for provider ids owned by this plugin. Keys should also appear in top-level providers. |
aliases | Record<string, object> | Provider aliases that should resolve to an owned provider for catalog or suppression planning. |
suppressions | object[] | Model rows from another source that this plugin suppresses for a provider-specific reason. |
discovery | Record<string, "static" | "refreshable" | "runtime"> | Whether the provider catalog can be read from manifest metadata, refreshed into cache, or requires runtime. |
runtimeAugment | boolean | Set to true only when the provider runtime must append catalog rows after manifest/config planning. |
aliases takes part in provider ownership lookup for model-catalog planning. Alias targets must be top-level providers owned by the same plugin. When a provider-filtered list uses an alias, OpenClaw can read the owning manifest and apply alias API/base URL overrides without loading provider runtime. Aliases do not expand unfiltered catalog listings; broad lists emit the owning canonical provider rows only.
suppressions takes the place of the earlier provider runtime suppressBuiltInModel hook. Suppression entries take effect only when the plugin owns the provider, or when the provider is set as a modelCatalog.aliases key pointing to an owned one. Model resolution no longer triggers runtime suppression hooks.
Provider fields:
| Field | Type | What it means |
|---|---|---|
baseUrl | string | Optional fallback base URL applied to models listed in this provider catalog. |
api | ModelApi | Optional fallback API adapter used for models in this provider catalog. |
headers | Record<string, string> | Optional fixed headers attached to this provider catalog. |
defaultUtilityModel | string | Optional provider-suggested small model identifier for brief internal tasks (like titles or progress narration). It is consulted when agents.defaults.utilityModel is not set and this provider supplies the agent's main model. |
models | object[] | Required model entries. Any row missing an id gets skipped. |
Model fields:
| Field | Type | Description |
|---|---|---|
id | string | The model identifier used by the provider, excluding the provider/ prefix. |
name | string | A label for display, if provided. |
api | ModelApi | An API setting that applies only to this model, if specified. |
baseUrl | string | A base URL that overrides the default for this model, if set. |
headers | Record<string, string> | Static headers configured for this model, if any. |
input | Array<"text" | "image" | "document"> | Accepted input and output types. Any other modality gets ignored silently. |
reasoning | boolean | Indicates whether reasoning capabilities are present. |
contextWindow | number | The context window size reported by the provider. |
contextTokens | number | An optional runtime cap on context that applies when it differs from contextWindow. |
maxTokens | number | The output token limit, when that value is known. |
thinkingLevelMap | Record<string, string | null> | Overrides for model id or parameters tied to a specific thinking level, if any. |
cost | object | Pricing in USD per million tokens, optionally including tieredPricing. |
compat | object | Compatibility flags that mirror OpenClaw model config settings, if used. |
upstreamModel | string | An optional provider/model reference to the same upstream model in another bundled catalog. |
mediaInput | object | Input configuration per modality, currently limited to images. |
status | "available" | "preview" | "deprecated" | "disabled" | Visibility state. Use "suppress" only when the entry must be hidden entirely. |
statusReason | string | An optional note displayed when the status is not available. |
replaces | string[] | Older provider-local ids that this model replaces. |
replacedBy | string | The provider-local id that should be used in place of deprecated entries. |
tags | string[] | Stable labels that pickers and filters rely on. |
Fields for suppression:
| Field | Type | Description |
|---|---|---|
provider | string | The provider id of the upstream entry to hide. This plugin must own it or list it as an owned alias. |
model | string | The provider-local model id that gets hidden. |
reason | string | An optional note shown when someone requests the hidden entry directly. |
when.baseUrlHosts | string[] | Optional provider base URL hosts that must be active for the suppression to take effect. |
when.providerConfigApiIn | string[] | Optional exact values for the provider-config api that must be present for the suppression to apply. |
upstreamModel designates a row that points to the same upstream model as a differently named row in another bundled catalog, such as a subscription endpoint alongside the vendor's API endpoint. This is authoring metadata: normalization discards it, and a contract test relies on it to prevent capability flags like compat.codeMode from diverging across catalogs that share the model. Most rows don't need a marker, since matching ignores a leading vendor namespace and casing: moonshotai/kimi-k3 and zai-org/GLM-5.2 already align with the first-party kimi-k3 and glm-5.2 rows. Only when the vendor's names are truly distinct should you turn to upstreamModel. Check Code mode.
Avoid placing runtime-only data in modelCatalog. Use static only when manifest rows are sufficiently complete for provider-filtered list and picker surfaces to bypass registry/runtime discovery. Use refreshable when manifest rows serve as useful listable seeds or supplements, but a refresh/cache can later add more rows; refreshable rows are not authoritative on their own. Use runtime when OpenClaw must load provider runtime to determine the list.
modelIdNormalization reference
Use modelIdNormalization for inexpensive provider-owned model-id cleanup that must run before provider runtime loads. This keeps aliases like short model names, provider-local legacy ids, and proxy prefix rules in the owning plugin manifest rather than in core model-selection tables.
{
"providers": ["anthropic", "openrouter"],
"modelIdNormalization": {
"providers": {
"anthropic": {
"aliases": {
"sonnet-4.6": "claude-sonnet-4-6"
}
},
"openrouter": {
"prefixWhenBare": "openrouter"
}
}
}
}
Provider fields:
| Field | Type | What it means |
|---|---|---|
aliases | Record<string,string> | Case-insensitive exact model-id aliases. Values are returned as written. |
stripPrefixes | string[] | Prefixes to remove before alias lookup, useful for legacy provider/model duplication. |
prefixWhenBare | string | Prefix to add when the normalized model id does not already contain /. |
prefixWhenBareAfterAliasStartsWith | object[] | Conditional bare-id prefix rules after alias lookup, keyed by modelPrefix and prefix. |
providerEndpoints reference
Use providerEndpoints for endpoint classification that generic request policy must know before provider runtime loads. Core still owns the meaning of each endpointClass; plugin manifests own the host and base URL metadata.
Officially externalized provider plugins are excluded from the core dist, so
their manifests are invisible until installed. Their providerEndpoints must
also be mirrored in scripts/lib/official-external-provider-catalog.json so
endpoint classification keeps working without the plugin; a contract test
enforces the mirror.
Endpoint fields:
| Field | Type | What it means |
|---|---|---|
endpointClass | string | Known core endpoint class, such as openrouter, moonshot-native, or google-vertex. |
hosts | string[] | Exact hostnames that map to the endpoint class. |
hostSuffixes | string[] | Host suffixes that map to the endpoint class. Prefix with . for domain suffix-only matching. |
baseUrls | string[] | Exact normalized HTTP(S) base URLs that map to the endpoint class. |
googleVertexRegion | string | Static Google Vertex region for exact global hosts. |
googleVertexRegionHostSuffix | string | Suffix to strip from matching hosts to expose the Google Vertex region prefix. |
providerRequest reference
Use providerRequest for cheap request-compatibility metadata that generic request policy needs without loading provider runtime. Keep behavior-specific payload rewriting in provider runtime hooks or shared provider-family helpers.
{
"providerRequest": {
"providers": {
"vllm": {
"family": "vllm",
"openAICompletions": {
"supportsStreamingUsage": true
}
}
}
}
}
Provider fields:
| Field | Type | What it means |
|---|---|---|
family | string | Provider family label used by generic request compatibility decisions and diagnostics. |
compatibilityFamily | "moonshot" | Optional provider-family compatibility bucket for shared request helpers. |
openAICompletions | object | OpenAI-compatible completions request flags, currently supportsStreamingUsage. |
secretProviderIntegrations reference
Use secretProviderIntegrations when a plugin can publish a reusable SecretRef exec provider preset. OpenClaw reads this metadata before plugin runtime loads, stores plugin ownership in secrets.providers.<alias>.pluginIntegration, and leaves actual secret resolution to the SecretRef runtime. Presets are exposed only for bundled plugins and installed plugins discovered from the managed plugin install roots, such as git and ClawHub installs.
{
"secretProviderIntegrations": {
"secret-store": {
"providerAlias": "team-secrets",
"displayName": "Team secrets",
"source": "exec",
"command": "${node}",
"args": ["./bin/resolve-secrets.mjs"]
}
}
}
The integration id serves as the map key. When providerAlias is not provided, OpenClaw treats the integration id as the SecretRef provider alias. Provider aliases need to conform to the standard SecretRef provider alias format, such as team-secrets or onepassword-work.
Upon preset selection by an operator, OpenClaw generates a provider reference in this form:
{
"secrets": {
"providers": {
"team-secrets": {
"source": "exec",
"pluginIntegration": {
"pluginId": "acme-secrets",
"integrationId": "secret-store"
}
}
}
}
}
During startup or reload, OpenClaw resolves that provider by pulling the latest plugin manifest metadata, verifying the owning plugin is both installed and active, and building the exec command from the manifest contents. If the plugin gets disabled or removed, the provider is revoked for any active SecretRefs. For standalone exec setups, operators can still create manual command/args providers on their own.
At this time, only source: "exec" presets are supported. command has to be ${node}, while args[0] needs to be a ./ resolver script that is relative to the plugin root. OpenClaw resolves it at startup or reload to the current Node executable along with the absolute in-plugin script path. Node options like --require, --import, --loader, --env-file, --eval, and --print fall outside the manifest preset contract. Operators requiring non-Node commands can set up standalone manual exec providers directly.
For manifest presets, OpenClaw computes trustedDirs from the plugin root, and for ${node} presets, from the current Node executable directory. Any trustedDirs authored in the manifest are disregarded. Additional exec provider options, including timeoutMs, noOutputTimeoutMs, maxOutputBytes, jsonOnly, env, and passEnv, are forwarded to the standard SecretRef exec provider configuration.
modelPricing reference
When the hosted catalog publisher needs provider-specific pricing-key behavior, modelPricing is the tool to use. The publisher accesses this metadata without importing any provider runtime code.
{
"providers": ["ollama", "openrouter"],
"modelPricing": {
"providers": {
"ollama": {
"external": false
},
"openrouter": {
"openRouter": {
"passthroughProviderModel": true
},
"liteLLM": false
}
}
}
}
Provider fields:
| Field | Type | What it means |
|---|---|---|
external | boolean | Assign false for local or self-hosted providers that must never rely on published external pricing. |
openRouter | false | object | OpenRouter publication-key mapping. Setting false turns off OpenRouter matching for this provider. |
liteLLM | false | object | LiteLLM publication-key mapping. Setting false turns off LiteLLM matching for this provider. |
Source fields:
| Field | Type | What it means |
|---|---|---|
provider | string | External catalog provider id when it differs from the OpenClaw provider id, for instance z-ai for a zai provider. |
passthroughProviderModel | boolean | Treat model ids containing slashes as nested provider/model refs, which helps proxy providers like OpenRouter. |
modelIdTransforms | "version-dots"[] | Additional external catalog model-id variants. version-dots attempts dotted version ids such as claude-opus-4.6. |
OpenClaw Provider Index
The OpenClaw Provider Index is preview metadata owned by OpenClaw for providers whose plugins might not be installed yet. It does not belong to any plugin manifest. Plugin manifests continue to be the authority for installed plugins. The Provider Index acts as the internal fallback contract that upcoming installable-provider and pre-install model picker surfaces will rely on when a provider plugin is absent.
Catalog authority order:
- User config.
- Installed plugin manifest
modelCatalog. - Model catalog cache from explicit refresh.
- OpenClaw Provider Index preview rows.
The Provider Index must avoid storing secrets, enabled state, runtime hooks, or live account-specific model data. Its preview catalogs adopt the same modelCatalog provider row shape as plugin manifests, yet should stick to stable display metadata unless runtime adapter fields such as api, baseUrl, pricing, or compatibility flags are deliberately kept in sync with the installed plugin manifest. Providers that rely on live /models discovery should push refreshed rows through the explicit model catalog cache path rather than having normal listing or onboarding call provider APIs.
Provider Index entries can also include installable-plugin metadata for providers whose plugin has left core or is otherwise not yet installed. This metadata follows the channel catalog pattern: package name, npm install spec, expected integrity, and basic auth-choice labels suffice to present an installable setup option. Once the plugin is installed, its manifest takes precedence and the Provider Index entry is disregarded for that provider.
openclaw doctor --fix handles the migration of a limited set of legacy top-level manifest capability keys into contracts.*: speechProviders, mediaUnderstandingProviders, imageGenerationProviders, and tools. These keys, along with any other capability lists, are no longer read as top-level manifest fields; standard manifest loading only recognizes them within contracts.
Manifest versus package.json
Each of the two files has a distinct purpose:
| File | Use it for |
|---|---|
openclaw.plugin.json | Discovery, config validation, auth-choice metadata, and UI hints that must exist before plugin code runs |
package.json | npm metadata, dependency installation, and the openclaw block used for entrypoints, install gating, setup, or catalog metadata |
When uncertain about where a piece of metadata belongs, apply this guideline:
- if OpenClaw needs it before plugin code is loaded, place it in
openclaw.plugin.json - if it concerns packaging, entry files, or npm install behavior, place it in
package.json
package.json fields that affect discovery
Certain pre-runtime plugin metadata is intentionally stored in package.json under the openclaw block rather than in openclaw.plugin.json. openclaw.bundle and openclaw.bundle.json are not part of OpenClaw's plugin contracts; native plugins must rely on openclaw.plugin.json along with the supported package.json#openclaw fields listed below.
Key examples:
| Field | What it means |
|---|---|
openclaw.extensions | Declares native plugin entrypoints. Must stay inside the plugin package directory. |
openclaw.runtimeExtensions | Declares built JavaScript runtime entrypoints for installed packages. Must stay inside the plugin package directory. |
openclaw.setupEntry | Lightweight setup-only entrypoint used during onboarding, channel setup, and read-only channel status/SecretRef discovery. Must stay inside the plugin package directory. |
openclaw.runtimeSetupEntry | Declares the built JavaScript setup entrypoint for installed packages. Requires setupEntry, must exist, and must stay inside the plugin package directory. |
openclaw.channel | Cheap channel catalog metadata like labels, docs paths, aliases, and selection copy. |
openclaw.channel.approvalFlags | Closed approval behavior flags available before runtime load. native means the channel owns native approval UI and same-turn resolution. |
openclaw.channel.commands | Static native command and native skill auto-default metadata used by config, audit, and command-list surfaces before channel runtime loads. |
openclaw.channel.cliAddOptions | Plugin-owned openclaw channels add options. Each entry declares flags, description, optional defaultValue, and optional valueType (int or list) for generic input coercion. |
openclaw.channel.configuredState | Lightweight configured-state checker metadata that can answer "does env-only setup already exist?" without loading the full channel runtime. |
openclaw.channel.persistedAuthState | Lightweight persisted-auth checker metadata that can answer "is anything already signed in?" without loading the full channel runtime. |
openclaw.install.clawhubSpec / openclaw.install.npmSpec / openclaw.install.localPath | Install/update hints for bundled and externally published plugins. |
openclaw.install.defaultChoice | Preferred install path when multiple install sources are available. |
openclaw.install.minHostVersion | Minimum supported OpenClaw host version, using a semver floor like >=2026.3.22 or >=2026.5.1-beta.1. |
openclaw.compat.pluginApi | Minimum OpenClaw plugin API range required by this package, using a semver floor like >=2026.5.27. |
openclaw.install.expectedIntegrity | Expected npm dist integrity string such as sha512-...; install and update flows verify the fetched artifact against it. |
openclaw.install.allowInvalidConfigRecovery | Allows a narrow bundled-plugin reinstall recovery path when config is invalid. |
openclaw.install.requiredPlatformPackages | npm package aliases that must materialize when their lockfile platform constraints match the current host. |
Before runtime loads, manifest metadata determines which provider, channel, and setup options appear during onboarding. package.json#openclaw.install instructs onboarding on how to fetch or enable a plugin when the user selects one of those options. Install hints must not be moved into openclaw.plugin.json.
Configured startup plugins register HTTP routes from their full runtime only after the Gateway begins listening. Until startup sidecars are ready, an otherwise-unclaimed HTTP request returns 503 with Retry-After: 1; core routes stay available throughout the startup process.
For openclaw.channel.cliAddOptions, use Commander's long-option syntax, such as --initial-sync-limit <n>. Configure valueType: "int" to handle a non-negative integer, or use valueType: "list" to break comma-, semicolon-, or newline-separated input into strings before the plugin setup adapter receives it. Leave out valueType to forward the parsed Commander value without modification.
During install and manifest registry loading for non-bundled plugin sources, openclaw.install.minHostVersion is enforced. Invalid values get rejected; newer-but-valid values cause external plugins to be skipped on older hosts. Bundled source plugins are treated as co-versioned with the host checkout.
For npm packages that expose required native binaries through optional, platform-specific aliases, use openclaw.install.requiredPlatformPackages. Provide the bare npm package name for each supported platform alias. During npm install, OpenClaw checks only the declared alias whose lockfile constraints match the current host. If npm reports success but that alias is absent, OpenClaw retries once with a fresh cache and rolls back the install if the alias remains missing.
openclaw.compat.pluginApi is enforced during package install for non-bundled plugin sources. Apply it for the minimum OpenClaw plugin SDK/runtime API version the package was built against. It may be stricter than minHostVersion when a plugin package requires a newer API while still maintaining a lower install hint for other flows. Official OpenClaw release sync bumps existing official plugin API floors to the OpenClaw release version by default, but plugin-only releases can keep a lower floor when the package deliberately supports older hosts. Do not rely on the package version alone as the compatibility contract. peerDependencies.openclaw remains npm package metadata; OpenClaw uses the openclaw.compat.pluginApi contract for install compatibility decisions.
For official install-on-demand metadata, use clawhubSpec when the plugin is published on ClawHub; onboarding treats that as the preferred remote source and records ClawHub artifact facts after install. npmSpec remains the compatibility fallback for packages that have not yet moved to ClawHub.
Exact npm version pinning already resides in npmSpec, for example "npmSpec": "@wecom/wecom-openclaw-plugin@1.2.3". Official external catalog entries should pair exact specs with expectedIntegrity so update flows fail closed if the fetched npm artifact no longer matches the pinned release. Interactive onboarding still offers trusted registry npm specs, including bare package names and dist-tags, for compatibility. Catalog diagnostics can distinguish exact, floating, integrity-pinned, missing-integrity, package-name mismatch, and invalid default-choice sources. They also warn when expectedIntegrity is present but there is no valid npm source it can pin. When expectedIntegrity is present, install/update flows enforce it; when omitted, the registry resolution is recorded without an integrity pin.
Channel plugins should provide openclaw.setupEntry when status, channel list, or SecretRef scans need to identify configured accounts without loading the full runtime. The setup entry should expose channel metadata plus setup-safe config, status, and secrets adapters; keep network clients, gateway listeners, and transport runtimes in the main extension entrypoint.
Runtime entrypoint fields do not override package-boundary checks for source entrypoint fields. For example, openclaw.runtimeExtensions cannot make an escaping openclaw.extensions path loadable.
openclaw.install.allowInvalidConfigRecovery is deliberately narrow. It does not make arbitrary broken configs installable. Today it only allows install flows to recover from specific stale bundled-plugin upgrade failures, such as a missing bundled plugin path or a stale channels.<id> entry for that same bundled plugin. Unrelated config errors still block install and send operators to openclaw doctor --fix.
openclaw.channel.persistedAuthState is package metadata for a tiny checker module:
{
"openclaw": {
"channel": {
"id": "whatsapp",
"persistedAuthState": {
"specifier": "./auth-presence",
"exportName": "hasAnyWhatsAppAuth"
}
}
}
}
Use it when setup, doctor, status, or read-only presence flows need a cheap yes/no auth probe before the full channel plugin loads. Persisted auth state is not configured channel state: do not use this metadata to auto-enable plugins, repair runtime dependencies, or decide whether a channel runtime should load. The target export should be a small function that reads persisted state only; do not route it through the full channel runtime barrel.
openclaw.channel.configuredState supports cheap configured checks. Prefer declarative env metadata when environment variables are sufficient:
{
"openclaw": {
"channel": {
"id": "telegram",
"configuredState": {
"env": {
"allOf": ["TELEGRAM_BOT_TOKEN"]
}
}
}
}
}
Use env.allOf when every listed variable is required and env.anyOf when any one non-empty variable is enough. If a tiny non-runtime check needs more than environment metadata, use specifier plus exportName as shown for persistedAuthState; when env is present, OpenClaw uses it without loading that module. If the check needs full config resolution or the real channel runtime, keep that logic in the plugin config.hasConfiguredState hook instead.
Discovery precedence (duplicate plugin ids)
OpenClaw discovers plugins from three roots, checked in this order: bundled plugins shipped with OpenClaw, the global install root (~/.openclaw/extensions), and the current workspace root (<workspace>/.openclaw/extensions), plus any explicit plugins.load.paths entries.
If two discoveries share the same id, only the highest-precedence manifest is kept; lower-precedence duplicates are dropped instead of loading beside it. Precedence, highest to lowest:
- Config-selected, a path explicitly pinned in
plugins.entries.<id> - Global install matching a tracked install record, a plugin installed via
openclaw plugin install/openclaw plugin updatethat OpenClaw's install tracking recognizes for that same id, even when the id also belongs to a bundled plugin - Bundled, plugins shipped with OpenClaw
- Workspace, plugins discovered relative to the current workspace
- Any other discovered candidate
Implications:
- A forked or stale copy of a bundled plugin sitting untracked in the workspace or global root will not shadow the bundled build.
- To override a bundled plugin, either run
openclaw plugin installfor that id so the tracked global install outranks the bundled copy, or pin a specific path viaplugins.entries.<id>so it wins by config-selected precedence. - Duplicate drops are logged so Doctor and startup diagnostics can point at the discarded copy.
- Config-selected duplicate overrides are worded as explicit overrides in diagnostics, but still warn so stale forks and accidental shadows stay visible.
JSON Schema requirements
- Every plugin must ship a JSON Schema, even if it accepts no config.
- An empty schema is acceptable (for example,
{ "type": "object", "additionalProperties": false }). - Schemas are validated at config read/write time, not at runtime.
- When extending or forking a bundled plugin with new config keys, update that plugin's
openclaw.plugin.jsonconfigSchemaat the same time. Bundled plugin schemas are strict, so addingplugins.entries.<id>.config.myNewKeyin user config without addingmyNewKeytoconfigSchema.propertieswill be rejected before the plugin runtime loads.
Example schema extension:
{
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"myNewKey": {
"type": "string"
}
}
}
}
Validation behavior
- Encountering an
channels.*key that isn't recognized counts as an error, unless a plugin manifest declares the channel id. When that same id shows up inplugins.allow,plugins.entries, orplugins.installs(a plugin that is referenced but not currently discoverable), OpenClaw reduces this to a warning. - References in
plugins.entries.<id>,plugins.allow, andplugins.denyto plugin ids that don't exist are treated as warnings ("stale config entry ignored") rather than errors, so gateway startup isn't blocked by upgrades or plugins that have been removed or renamed. - An
plugins.slots.memorythat points to an unknown plugin id results in an error, with the exception of the knownmemory-lancedbofficial external plugin, which produces a warning. - When a plugin is installed but its manifest or schema is missing or corrupted, validation fails and Doctor reports the plugin error.
- If plugin config exists but the plugin is disabled, the config is retained and a warning appears in Doctor and the logs.
Check the Configuration reference for the complete plugins.* schema.
Notes
- Native OpenClaw plugins, including those loaded from the local filesystem, must have a manifest. The runtime still loads the plugin module separately; the manifest serves only discovery and validation purposes.
- Native manifests are parsed using JSON5, meaning comments, trailing commas, and unquoted keys are fine as long as the final value remains an object.
- The manifest loader reads only documented manifest fields. Avoid adding custom top-level keys.
- When a plugin doesn't require them,
channels,providers,cliBackends, andskillscan all be left out. providerCatalogEntryshould remain lightweight and avoid importing broad runtime code; reserve it for static provider catalog metadata or narrow discovery descriptors, not request-time execution.- Exclusive plugin kinds are chosen via
plugins.slots.*:kind: "memory"throughplugins.slots.memory(defaulting tomemory-core), andkind: "context-engine"throughplugins.slots.contextEngine(defaulting tolegacy). - Declare the exclusive plugin kind in this manifest. The runtime-entry
OpenClawPluginDefinition.kindis deprecated and remains only as a compatibility fallback for older plugins. - Env-var metadata in
setup.providers[].envVarsis purely declarative. Status, audit, cron delivery validation, and other read-only surfaces still apply plugin trust and effective activation policy before treating an env var as configured. - For runtime wizard metadata that needs provider code, refer to Provider runtime hooks.
- If your plugin depends on native modules, document the build steps and any package-manager allowlist requirements (for example, pnpm
allow-build-scripts+pnpm rebuild <package>).
Related
-
Building plugins, A starting point for working with plugins.
-
Plugin architecture, The internal architecture and capability model.
-
SDK overview, Plugin SDK reference and subpath imports.