openclaw.plugin.json. For compatible bundle layouts (Agent Plugins, Codex, Claude, Cursor), see Plugin bundles.
Compatible bundle formats use their own manifest files instead:
- Agent Plugins bundle:
plugin.jsonat the package root, per the open Agent Plugins standard - Codex bundle:
.codex-plugin/plugin.json - Claude bundle:
.claude-plugin/plugin.json, or the default Claude component layout with no manifest - Cursor bundle:
.cursor-plugin/plugin.json
openclaw.plugin.json schema below. For a compatible bundle, OpenClaw reads bundle metadata, declared skill roots, Claude command roots, Claude settings.json defaults, Claude LSP defaults, and supported hook packs, when the layout matches OpenClaw’s runtime expectations.
Every native OpenClaw plugin must ship openclaw.plugin.json in the plugin root. OpenClaw reads it to validate configuration without executing plugin code. A missing or invalid manifest blocks config validation and is treated as a plugin error.
See Plugins for the full plugin system guide, and Capability model for the native capability model and current external-compatibility guidance.
What this file does
openclaw.plugin.json is metadata OpenClaw reads before loading your plugin code. Everything in it must be cheap enough to inspect without booting plugin runtime.
Use it 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
- durable and regenerable state- or agent-relative backup resources
- QA runner metadata the shared
openclaw qahost can inspect - channel-specific config metadata merged into catalog and validation surfaces
package.json.
Minimal example
Rich example
Top-level field reference
Plugin icon
Place the portable plugin icon atassets/icon.png, relative to the plugin root. No manifest
field is required. Use a square PNG that remains recognizable at 16 px; 512×512 is recommended.
Missing, unreadable, or invalid icons are ignored and do not invalidate the plugin.
OpenClaw adopts this fixed package path as its icon convention, matching the path proposed in
Agent Plugins 1.1. Other Agent Plugins
consumers may not discover it unless that proposal is adopted. The fixed path keeps packages
portable and inspectable, avoids manifest path indirection and precedence rules, and lets OpenClaw
render the icon without a runtime network request. Top-level plugin-branding icon URLs are not
loaded; provider-auth artwork remains server-owned catalog metadata.
Prefer top-level sessionRouteStateOwners for static doctor ownership. The
older doctorContract.sessionRouteStateOwners: true declaration plus a
sessionRouteStateOwners export from doctor-contract-api remains supported
for external plugins, but is deprecated. When the manifest field is present,
OpenClaw uses it without loading the doctor-contract module. Removal plan:
remove the module fallback in OpenClaw 2027.1 after the external-plugin
migration window.
Set doctorContract.configRepair: true when the doctor-contract module exports
non-empty legacyConfigRules, a normalizeCompatibilityConfig function, or
both. One declaration covers the complete config-repair artifact.
The Codex plugin sets doctorHealthChecks: true when its public API exports
health-check registration. Doctor checks the selected plugin’s trust before
loading this surface. Older installed versions without the declaration skip
Codex health registration without preventing other checks; a declared but
missing or broken API remains an error. This does not grant plugin capabilities
or replace upgrade consent.
Channel plugins maintained in the OpenClaw source tree also expose these config
exports through a pure config-doctor-api.ts entrypoint. The core package retains
that entrypoint alongside its channel schemas when the plugin runtime is
distributed separately. This lets doctor --fix migrate older configuration
before plugin installation or capability consent. An installed plugin’s doctor
contract remains authoritative; retained entrypoints do not expose state
migrations, install plugins, or grant capabilities.
backupResources reference
UsebackupResources to declare plugin-owned durable data that backups must
include, or generated data that OpenClaw can safely omit and regenerate after
restore. The backup planner reads this metadata without loading plugin runtime
or modifying plugin files. Only effectively activated, loadable plugins
contribute resources; disabled or unloadable plugins cannot exclude data.
Plugin identity and its trusted root come from manifest discovery; resource
entries cannot declare or override an owner.
relativePath must not be empty
or absolute and must not contain backslashes, NULs, empty path segments, .,
.., Windows drive or UNC prefixes, URI-like values, or any path that escapes
its selected anchor. Invalid entries are rejected rather than normalized.
The planner deduplicates resources deterministically. A narrower regenerable
declaration wins over a broad configured state or agent root. Among plugin
resource declarations, only an explicit nested include protects a descendant
and keeps its excluded ancestors traversable. Explicit config, credentials,
workspace, and nested agent paths also remain protected. Omit only data the
plugin can recreate.
openclaw backup create --only-config does not inspect plugin backup metadata.
MCP server reference
mcpServers lets a native plugin ship an MCP server, including an MCP App, without requiring operators to duplicate its static process definition in openclaw.json:
command, args, cwd, and workingDirectory paths resolve from the plugin root. User configuration remains authoritative: mcp.servers.<name> can replace a plugin default or set enabled: false to omit it. MCP App rendering and server-tool calls still require the normal MCP Apps setting and effective tool policy; declaring a server does not bypass either boundary.
controlUi reference
controlUi declares a trusted native browser entry and optional stylesheets for
the Control UI. Paths are relative to the plugin root and must name compiled
JavaScript and CSS. Assets follow the Gateway’s authentication policy, are
captured as immutable revisions, and refresh only through the explicit UI reload
flow.
User-installed native UI requires Settings → Labs → Custom plugin UI
(gateway.controlUi.experimental.customPlugins, default false). Native UI
from enabled bundled plugins remains available. See
Enable custom plugin UI for
restart and browser reload requirements. This gate does not disable the
plugin’s backend APIs or the sandboxed dashboard bindings below.
package.json.openclaw.controlUi for the source entry and let
openclaw plugins build generate this declaration. Native UI executes with the
browser application’s trust; it is distinct from the scoped dashboard widget
bindings below. See Feature plugins for authoring,
replacements, reload, and activation receipts.
dashboard reference
dashboard lets an enabled plugin expose existing Gateway RPCs to granted dashboard widgets without adding plugin policy to core. Data bindings must name a method the same plugin registers with operator.read; action verbs must name a method it registers with operator.write. A mismatch rejects the plugin during registration.
<plugin-id>.<id>, such as 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 keep the natural form. paramShape is an optional JSON Schema applied to the action params object before OpenClaw invokes the plugin RPC.
catalog reference
catalog provides optional display hints to plugin browsers. Hosts may ignore these hints. They never install or enable the plugin, and they do not change its runtime behavior or trust level.
Generation provider metadata reference
The generation provider metadata fields describe static auth signals for providers declared in the matchingcontracts.*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.
Each
configSignals entry supports:
Each
mode guard supports:
Each
authSignals entry supports:
Each
providerBaseUrl guard supports:
Tool metadata reference
toolMetadata uses the same configSignals and authSignals shapes as generation provider metadata, keyed by tool name. contracts.tools declares ownership. toolMetadata declares cheap availability evidence so OpenClaw can avoid importing a plugin runtime just to have its tool factory return null.
toolMetadata entries also accept:
profiles: built-in tool profiles that expose the plugin tool by default. Valid values areminimal,coding,messaging, andfull. These contributions merge into the corresponding profile allowlist; explicit operator allowlists and deny rules remain authoritative.optional: marks the tool as non-required for plugin activation.replaySafe: marks tool execution as safe to repeat after an incomplete model turn.sideEffecting: marks execution as potentially changing durable or external state.
configSignals and authSignals fields above.
If a tool has no toolMetadata, OpenClaw preserves the existing behavior and loads the owning plugin when the tool contract matches policy. For hot-path tools whose factory depends on auth/config, plugin authors should declare toolMetadata instead of making core import runtime to ask.
providerAuthChoices reference
EachproviderAuthChoices entry describes one onboarding or auth choice. OpenClaw reads this before provider runtime loads. Provider setup lists use these manifest choices, descriptor-derived setup choices, and install-catalog metadata without loading provider runtime.
When
appGuidedDiscovery is true, the matching provider auth method must expose
appGuidedSetup.detect and appGuidedSetup.prepare. Detection must be
read-only: no login, model pull, download, or config write. Preparation rechecks
the exact selected model and returns a config proposal; OpenClaw live-tests that
proposal in isolation and commits it only after success. A provider can also
expose appGuidedSetup.detectAvailability to mark its setup choice as detected
when the local service is reachable but no model qualifies for automatic setup.
The availability probe is also read-only.
When personalAccount is true, the method runs through the shared wizard protocol
with a credential-free environment/config, no agent directory or preseeded secret,
and plaintext input mode. It must return exactly one inline credential for its
provider and honor cancellation. It must not import a native CLI login, resolve a
SecretRef, write credentials/config, or require shared model activation. The
Gateway owns the private per-person commit; configPatch, defaultModel, and the
returned shared profile id are not applied. Mark credential prompts sensitive.
Use this capability only when the provider permits this credential use.
Personal-account calls always supply ctx.assertCurrent. Preserve this
closure-bound check through provider helpers and invoke it immediately before
external effects, including discovery, polling and token exchange after any
interactive or asynchronous wait. With fetchWithSsrFGuard, pass it as
beforeRequest so it runs after DNS/proxy preparation and on redirects. Keep
forwarding ctx.signal to cancel in-flight work; a signal alone does not recheck
the person’s current permission. Standalone CLI/onboarding calls may omit the
check because they do not carry a Gateway person’s authority.
An optional matchesPersonalAccount(credential, existing) auth-method hook can
prove that an OAuth reconnect is the same provider account. Match the complete
identity, not an email or a shared workspace alone. Without that proof, a new
OAuth account slot is created and old chat pins retain their original credential.
API keys and static tokens reuse a slot only when their literal value matches.
cliCommands reference
Declare every plugin-owned root command incliCommands so root help and command-owner routing stay metadata-only:
api.registerCli(..., { descriptors: [...] }); runtime descriptors may additionally provide machineOutput. Nested commands such as openclaw nodes <feature> are not root commands and do not belong in cliCommands.
commandAliases reference
UsecommandAliases when a plugin owns a runtime command name that users may mistakenly put in plugins.allow or try to run as a root CLI command. OpenClaw uses this metadata for diagnostics without importing plugin runtime code.
If a plugin fails to load, invoking its declared runtime-slash command in chat returns the plugin name, a short failure reason, and recovery guidance (openclaw doctor and gateway logs). Unknown commands and commands belonging to intentionally disabled plugins keep their normal handling; manifest ownership alone does not make a command executable.
activation reference
Useactivation when the plugin can cheaply declare which control-plane events should include it in an activation/load plan.
This block is planner metadata, not a lifecycle API. It does not register runtime behavior, does not replace register(...), and does not promise that plugin code has already executed. The activation planner uses these fields to narrow candidate plugins before falling back to existing manifest ownership metadata such as providers, channels, commandAliases, setup.providers, contracts.tools, and hooks.
Prefer the narrowest metadata that already describes ownership. Use providers, channels, commandAliases, setup descriptors, or contracts when those fields express the relationship. Use activation for extra planner hints that cannot be represented by those ownership fields. Use top-level cliBackends for CLI runtime aliases such as claude-cli, my-cli, or google-gemini-cli; activation.onAgentHarnesses is only for embedded agent harness ids that do not already have an ownership field.
Every plugin should set activation.onStartup intentionally. Set it to true only when the plugin must run during Gateway startup. Set it to false when the plugin is inert at startup and should load only from narrower triggers. Omitting onStartup no longer startup-loads the plugin implicitly; use explicit activation metadata for startup, channel, config, agent-harness, memory, or other narrower activation triggers.
Current live consumers:
- Gateway startup planning uses
activation.onStartupfor explicit startup import. - Command-triggered CLI planning falls back to legacy
commandAliases[].cliCommandorcommandAliases[].name. - Agent-runtime startup planning uses
activation.onAgentHarnessesfor embedded harnesses and top-levelcliBackends[]for CLI runtime aliases. - Channel-triggered setup/channel planning falls back to legacy
channels[]ownership when explicit channel activation metadata is missing. - Startup plugin planning uses
activation.onConfigPathsfor non-channel root config surfaces such as the bundled browser plugin’sbrowserblock. - Provider-triggered setup/runtime planning falls back to legacy
providers[]and top-levelcliBackends[]ownership when explicit provider activation metadata is missing.
activation-command-hint means activation.onCommands matched, while manifest-command-alias means the planner used commandAliases ownership instead. These reason labels are for host diagnostics and tests; plugin authors should keep declaring the metadata that best describes ownership.
qaRunners reference
UseqaRunners when a plugin contributes one or more transport runners beneath
the shared openclaw qa root. Keep this metadata cheap and static; the plugin
runtime still owns actual CLI registration through a lightweight
qa-runner-api.ts surface that exports matching qaRunnerCliRegistrations. For
plugins using the shipped runtime-api.ts contract, that legacy surface remains
accepted through 2026-10-01 while authors migrate. An
optional adapterFactory exposes the transport to shared QA scenarios without
changing the registered command’s runner.
Module-backed flow scenarios are an adapter-owned execution form. Set
adapterFactory.supportsModuleFlows to true only when every adapter created
by that factory implements prepareFlow; QA planning excludes module flows
from implementations that do not declare support.
The
adapterFactory id must match commandName. Do not export registrations
for commands absent from the manifest.
setup reference
Usesetup when setup and onboarding surfaces need cheap plugin-owned metadata before runtime loads.
cliBackends stays valid and continues to describe CLI inference backends. setup.cliBackends is the setup-specific descriptor surface for control-plane/setup flows that should stay metadata-only.
When present, setup.providers and setup.cliBackends are the preferred descriptor-first lookup surface for setup discovery. If the descriptor only narrows the candidate plugin and setup still needs richer setup-time runtime hooks, set requiresRuntime: true and keep setup-api in place as the fallback execution path.
Without an explicit openclaw.setupEntry, OpenClaw resolves the conventional setup-api file at the package root or in package-local dist/. Standalone runtime builds include that public surface automatically.
OpenClaw includes setup.providers[].envVars in generic provider auth and env-var lookups. Put 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 stay 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
authEvidence is for provider-owned local credential markers that can be verified without loading runtime code. These checks must stay cheap and local: no network calls, no keychain or secret-manager reads, no shell commands, and no provider API probes.
Supported evidence entries:
setup fields
uiHints reference
uiHints is a map from config field names to small rendering hints. Keys can use dots for nested config fields, but no path segment may be __proto__, constructor, or prototype; setup rejects those names.
Channel config sections inherit
help for the leaves every channel shares
(enabled, allowFrom, dmPolicy, groupPolicy, streaming, and similar) at
the channel root and under accounts.<id>. A channel that declares its own
help for one of those keys always wins, so override it whenever the shared
wording is wrong for your provider. Provider-specific keys such as credentials,
hosts, and webhooks still need their own hints.
contracts reference
Usecontracts only for static capability ownership metadata that OpenClaw can read without importing the plugin runtime.
speechProviders and realtimeVoiceProviders, list the canonical provider ID first, followed by any aliases scoped to that capability:
contracts.embeddedExtensionFactories is retained for bundled Codex app-server-only extension factories. Bundled tool-result transforms should declare contracts.agentToolResultMiddleware and register with api.registerAgentToolResultMiddleware(...) instead. Installed plugins may use the same middleware seam only when explicitly enabled and only for runtimes they declare in contracts.agentToolResultMiddleware.
Installed plugins that need the host-trusted pre-tool policy tier must declare each registered local id in contracts.trustedToolPolicies and be explicitly enabled. Bundled plugins keep the existing trusted-policy path, but installed plugins with undeclared policy ids are rejected before registration. Policy ids are scoped to the registering plugin, so two plugins may both declare and register workflow-budget; a single plugin may not register the same local id twice.
Runtime api.registerTool(...) registrations must match contracts.tools. Tool discovery uses this list to load only the plugin runtimes that can own the requested tools.
Provider plugins that implement resolveExternalAuthProfiles should declare contracts.externalAuthProviders; undeclared external-auth hooks are ignored.
Provider plugins that implement both resolveUsageAuth and fetchUsageSnapshot should declare each auto-discovered provider id in contracts.usageProviders. Usage discovery reads this contract before loading runtime code, then verifies 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. Registration requires resolveAllocation, provision, inspect, and destroy. The allocation resolver returns the exact operation cleanup handle and an explicit shared-host fact without creating or preparing a machine; see the worker provider contract. Core persists durable intent before calling provision; providers validate their settings and optional per-dispatch machineClass and executionMode before external allocation, and repeated calls with the same operation id must adopt the same lease without changing the selected mode. 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. Session-placement providers declare a closed, unique, canonically ordered supportedExecutionModes tuple: ["worker-turn"], ["remote-exec"], or ["worker-turn", "remote-exec"]. Empty lists, duplicates, unknown values, and noncanonical ordering are rejected. worker-turn requires a node lease; remote-exec accepts a node lease or an SSH lease. Omission advertises no session-placement modes while leaving direct lifecycle operations available. A direct environment create supplies no session execution mode; providers use their documented default, which is worker-turn for Crabbox. 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. The optional resolveDestroyTimeoutMs(profile) supplies the equivalent budget for requested teardown and bootstrap-failure cleanup, including snapshot capture before confirmed release. Both hooks must return positive safe integers within the platform timer limit; an explicit service timeout override takes precedence. 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 / dormant / 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 fences the environment and enters canonical teardown; it does not bypass the exact worker-stop acknowledgment required on shared or unknown hosts.
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
UseconfigContracts 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.
Each
dangerousFlags entry supports:
secretInputs supports:
Capability owners fail cold when their provider is unavailable, so a stale credential never remains active. Route owners may retain the last-known-good value while their full plugin config and provider definition stay unchanged.
For plugins declaring
secretInputs, configSchema validates the pre-resolution source config paired with the runtime config. A valid SecretRef is not rejected because its resolved credential is a string with a different shape. Invalid plaintext source values still fail validation. Runtime loading, CLI registration, and root command discovery use the same rule; plugins receive resolved values with defaults selected from the source config, without changing either input.
Concrete paths preserve literal record keys and array indices: headers["X.Trace"] remains distinct from headers.X.Trace, and record key ["0"] remains distinct from array index [0]. Plugin IDs containing dots are quoted the same way, such as plugins.entries["example.plugin"].config.headers["X.Trace"].
mediaUnderstandingProviderMetadata reference
UsemediaUnderstandingProviderMetadata 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.
channelConfigs reference
UsechannelConfigs 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>
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 can declare static auto defaults for command config checks that run before channel runtime loads. Bundled channels can also publish the same defaults through package.json#openclaw.channel.commands alongside their other package-owned channel catalog metadata.
Replacing another channel plugin
UsepreferOver when your plugin is the preferred owner for a channel id that another plugin can also provide. Common cases are a renamed plugin id, a standalone plugin that supersedes a bundled plugin, or a maintained fork that keeps the same channel id for config compatibility.
channels.chat is configured, OpenClaw considers both the channel id and the preferred plugin id. If the lower-priority plugin was only selected because it is bundled or enabled by default, OpenClaw disables it in the effective runtime config so one plugin owns the channel and its tools. Explicit user selection still wins: if the user explicitly enables both plugins (via plugins.allow or a material plugins.entries config), OpenClaw preserves that choice and reports duplicate channel/tool diagnostics instead of silently changing the requested plugin set.
Keep preferOver scoped to plugin ids that can really provide the same channel. It is not a general priority field and it does not rename user config keys.
modelSupport reference
UsemodelSupport when OpenClaw should infer your provider plugin from shorthand model ids like gpt-5.6-sol or claude-sonnet-4.6 before plugin runtime loads.
- explicit
provider/modelrefs use the owningprovidersmanifest metadata modelPatternsbeatmodelPrefixes- if one non-bundled plugin and one bundled plugin both match, the non-bundled plugin wins
- remaining ambiguity is ignored until the user or config specifies a provider
modelPatterns entries are compiled through compileSafeRegex, which rejects patterns containing nested repetition (for example (a+)+$). Patterns that fail the safety check are silently skipped, the same as syntactically invalid regex. Keep patterns simple and avoid nested quantifiers.
modelCatalog reference
UsemodelCatalog when OpenClaw should know provider model metadata before loading plugin runtime. This is the manifest-owned source for fixed catalog rows, publication-time metadata sources, provider aliases, suppression rules, and discovery mode. Runtime refresh still belongs in provider runtime code, but the manifest tells core when runtime is required.
modelsDev opts an owned provider into models.dev metadata hydration when the hosted catalog is published. Declare the upstream provider once per OpenClaw provider, not once per model. Omission means no models.dev hydration; there is no central provider fallback. Keys are normalized as OpenClaw provider ids and source ids are trimmed. Empty or non-string source ids and mappings for unowned providers are ignored; an alias alone does not grant ownership. A mapping does not create catalog provider rows or relax their validation.
Hydration adds eligible model ids and fills only undefined metadata. Explicit manifest values remain authoritative, including false; models.dev never supplies transport settings or prices. Prices still follow the provider-owned pricing policy. Opt in only when the provider defaults are appropriate for newly imported rows; providers that choose a transport per model should not opt in unless those defaults are safe. Hydration errors fail publication, leaving the last published artifact intact. The publisher hydrates opted-in metadata even without --pricing; that flag controls price enrichment only. A dry run performs the same metadata hydration without writing the artifact.
This field is publication-time authoring metadata, not a Gateway discovery hook. It does not add runtime network calls or hot reload; the existing hosted catalog update lifecycle is unchanged.
aliases participates 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 replaces the old provider runtime suppressBuiltInModel hook. Suppression entries are honored only when the provider is owned by the plugin or declared as a modelCatalog.aliases key that targets an owned provider. Runtime suppression hooks are no longer called during model resolution.
Provider fields:
Model fields:
Suppression fields:
upstreamModel marks a row that serves the same upstream model as a row in another bundled catalog under a different name, for example a subscription endpoint next to the vendor’s API endpoint. It is authoring metadata: normalization drops it, and a contract test uses it to keep capability flags such as compat.codeMode from drifting between catalogs that ship the same model. Most rows need no marker, because matching ignores a leading vendor namespace and casing: moonshotai/kimi-k3 and zai-org/GLM-5.2 already match the first-party kimi-k3 and glm-5.2 rows. Reach for upstreamModel only when the vendor’s own names genuinely differ. See Code mode.
Do not put runtime-only data in modelCatalog. Use static only when manifest rows are complete enough for provider-filtered list and picker surfaces to skip registry/runtime discovery. Use refreshable when manifest rows are useful listable seeds or supplements but a refresh/cache can add more rows later; refreshable rows are not authoritative by themselves. Use runtime when OpenClaw must load provider runtime to know the list.
Capabilities belong to the declared API and base URL, not only the provider/model id. When model listing enriches a cached row, it uses manifest capabilities only for a matching route; a custom endpoint must supply its own limits and capabilities.
modelIdNormalization reference
UsemodelIdNormalization for cheap provider-owned model-id cleanup that must happen before provider runtime loads. This keeps aliases such as short model names, provider-local legacy ids, and proxy prefix rules in the owning plugin manifest instead of in core model-selection tables.
providerEndpoints reference
UseproviderEndpoints 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:
providerRequest reference
UseproviderRequest 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.
secretProviderIntegrations reference
UsesecretProviderIntegrations 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.
providerAlias is omitted, OpenClaw uses the integration id as the SecretRef provider alias. Provider aliases must match the normal SecretRef provider alias pattern, for example team-secrets or onepassword-work.
When an operator selects the preset, OpenClaw writes a provider reference like:
command/args providers directly.
Only source: "exec" presets are currently supported. command must be ${node}, and args[0] must be a ./ plugin-root-relative resolver script. OpenClaw materializes it at startup/reload to the current Node executable and the absolute in-plugin script path. Node options such as --require, --import, --loader, --env-file, --eval, and --print are not part of the manifest preset contract. Operators who need non-Node commands can configure standalone manual exec providers directly.
OpenClaw derives trustedDirs for manifest presets from the plugin root and, for ${node} presets, the current Node executable directory. Manifest-authored trustedDirs are ignored. Other exec provider options such as timeoutMs, noOutputTimeoutMs, maxOutputBytes, jsonOnly, env, and passEnv pass through to the normal SecretRef exec provider config.
modelPricing reference
UsemodelPricing when the hosted catalog publisher needs provider-specific pricing-key behavior. The publisher reads this metadata without importing provider runtime code.
Source fields:
A declared provider policy enables only its declared source mappings. Without a
policy, publication tries OpenRouter, then LiteLLM. Each selected price is a
complete schedule: base rates and context tiers are never combined across sources.
OpenRouter’s native prompt-length overrides are supported; time-based overrides
are not represented as static context tiers.
For authoritative native source mappings, use:
pricing-api.ts artifacts share
payload parsing with runtime discovery without importing provider runtimes.
DeepInfra’s top-level array uses model_name identity. Its numeric discount and
cached-input ratio apply to native cents-per-token prices. Pricing prose,
nonempty tables, scheduled expiry, and undocumented generic cache-write rates
are validated but omitted as unsupported schedules. Priority/flex and explicit
cache-retention multipliers do not change standard costs. Its agent projection
continues to own runtime metadata; the pricing feed does not discover chat models.
An opted-in native source owns the complete provider schedule, including missing
prices: generic sources cannot fill its gaps. A successful feed with no price for
a bundled model preserves that model’s metadata, omits its cost, and emits a
publication warning. Missing pricing is not evidence of model retirement or free
usage. Explicit native zero prices remain known-free estimates. Fetch failure,
malformed response bodies or declared prices, and feeds with no usable prices
stop publication, leaving the previous hosted catalog intact. Explicit operator
rates remain unchanged. This authoring metadata adds no operator setting and does
not change the Gateway’s existing refresh and restart lifecycle.
OpenClaw Provider Index
The OpenClaw Provider Index is OpenClaw-owned preview metadata for providers whose plugins may not be installed yet. It is not part of a plugin manifest. Plugin manifests remain the installed-plugin authority. The Provider Index is the internal fallback contract that future installable-provider and pre-install model picker surfaces will consume when a provider plugin is not installed. Catalog authority order:- User config.
- Installed plugin manifest
modelCatalog. - Model catalog cache from explicit refresh.
- OpenClaw Provider Index preview rows.
modelCatalog provider row shape as plugin manifests, but should stay limited to stable display metadata unless runtime adapter fields such as api, baseUrl, pricing, or compatibility flags are intentionally kept aligned with the installed plugin manifest. Providers with live /models discovery should write refreshed rows through the explicit model catalog cache path instead of making normal listing or onboarding call provider APIs.
Provider Index entries may also carry installable-plugin metadata for providers whose plugin has moved out of core or is otherwise not installed yet. This metadata mirrors the channel catalog pattern: package name, npm install spec, expected integrity, and cheap auth-choice labels are enough to show an installable setup option. Once the plugin is installed, its manifest wins and the Provider Index entry is ignored for that provider.
openclaw doctor --fix migrates a small, closed set of legacy top-level manifest capability keys into contracts.*: speechProviders, mediaUnderstandingProviders, imageGenerationProviders, and tools. None of these (or any other capability list) are read as top-level manifest fields anymore; normal manifest loading only recognizes them under contracts.
Manifest versus package.json
The two files serve different jobs:
If you are unsure where a piece of metadata belongs, use this rule:
- if OpenClaw must know it before loading plugin code, put it in
openclaw.plugin.json - if it is about packaging, entry files, or npm install behavior, put it in
package.json
package.json fields that affect discovery
Some pre-runtime plugin metadata intentionally lives inpackage.json under the openclaw block instead of openclaw.plugin.json. openclaw.bundle and openclaw.bundle.json are not OpenClaw plugin contracts; native plugins must use openclaw.plugin.json plus the supported package.json#openclaw fields below.
Important examples:
Manifest metadata decides which provider/channel/setup choices appear in onboarding before runtime loads.
package.json#openclaw.install tells onboarding how to fetch or enable that plugin when the user picks one of those choices. Do not move install hints into openclaw.plugin.json.
Configured startup plugins register HTTP routes from their full runtime after the Gateway starts listening. Until startup sidecars are ready, an otherwise-unclaimed HTTP request returns 503 with Retry-After: 1; core routes remain available throughout startup.
For openclaw.channel.cliAddOptions, use Commander’s long-option syntax, such as --initial-sync-limit <n>. Set valueType: "int" to parse a non-negative integer or valueType: "list" to split comma-, semicolon-, or newline-delimited input into strings before the plugin setup adapter receives it. Omit valueType to pass the parsed Commander value through unchanged.
openclaw.install.minHostVersion is enforced during install and manifest registry loading for non-bundled plugin sources. Invalid values are rejected; newer-but-valid values skip external plugins on older hosts. Bundled source plugins are assumed to be co-versioned with the host checkout.
openclaw.install.requiredPlatformPackages is for npm packages that expose required native binaries through optional, platform-specific aliases. List the bare npm package name for every supported platform alias. During npm install, OpenClaw verifies only the declared alias whose lockfile constraints match the current host. If npm reports success but omits that alias, OpenClaw retries once with a fresh cache and rolls back the install if the alias is still missing.
openclaw.compat.pluginApi is enforced during package install for non-bundled plugin sources. Use it for the OpenClaw plugin SDK/runtime API floor that the package was built against. It can be stricter than minHostVersion when a plugin package needs a newer API but still keeps 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 intentionally supports older hosts. Do not use 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.
Official install-on-demand metadata should declare npmSpec as the default and clawhubSpec as the secondary source when both publish the same plugin. Default remote installs try npm first, then the declared ClawHub source only when the npm target is unavailable. A ClawHub-only plugin stays on ClawHub; OpenClaw never derives an npm package name from a ClawHub slug. Explicit source selections, exact versions, and non-latest tags remain authoritative. Doctor’s existing stale runtime repair can refresh an official plugin bound to the current OpenClaw release cohort on its recorded registry, retaining exact npm pin intent by recording the replacement version. Bare specs and @latest follow the active release-channel policy while retaining the requested selector in the install record. Integrity, compatibility, trust, install-policy, and capability-consent failures do not authorize switching sources.
Exact npm version pinning already lives 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 it is 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 intentionally 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.configuredState supports cheap configured checks. Prefer declarative env metadata when environment variables are sufficient:
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. A complete, non-empty specifier and exportName pair takes precedence over env. If either field is absent or blank, the probe uses its env metadata without loading a module. If the check needs full config resolution or the real channel runtime, keep that logic in the plugin config.hasConfiguredState hook instead.
For both state probes, OpenClaw builds rewrite source specifiers only for complete module pairs, naming the exact emitted JavaScript artifact, including its .js or .cjs extension. Env-backed incomplete pairs are preserved unchanged. Built checkout metadata uses paths relative to the plugin root; standalone packages use the plugin-local dist/ directory.
Discovery precedence (duplicate plugin ids)
OpenClaw discovers plugins from explicitplugins.load.paths entries, the current workspace root (<workspace>/.openclaw/extensions), bundled plugins shipped with OpenClaw, and global install locations (~/.openclaw/extensions plus tracked install paths). Discovery order alone does not determine which copy loads.
If two distinct plugin roots 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 selected in
plugins.load.paths - Development-source bundled — a bundled plugin inside the checkout selected by
OPENCLAW_DEV_SOURCE_ROOT - Global install matching a tracked install record — an installed global candidate whose path matches its install record, managed by
openclaw plugins install/openclaw plugins update - Bundled — other plugins shipped with OpenClaw
- Workspace — plugins discovered relative to the current workspace
- Untracked global — other plugins discovered in the global root
- An auto-discovered workspace or untracked global copy will not shadow a bundled plugin, even when its id is enabled or allowlisted.
plugins.allowandplugins.entries.<id>.enabledcontrol load permission, not source selection. - To override a bundled plugin intentionally, select its path via
plugins.load.paths. A tracked global install can also override an ordinary bundled copy, but not a development-source bundled copy. - Duplicate warnings identify the discarded copy and selected source, with config-selected winners labeled as explicit overrides. Intentional tracked-install overrides of ordinary bundled copies do not emit duplicate warnings.
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 }). - Config is validated against the manifest schema at config read/write time and before the plugin loads.
- 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.
Validation behavior
Capability catalogs
capabilityCatalogEntry declares a lightweight module relative to the selected
plugin root, for example "./capability-catalog.ts". It exports actual speech,
realtime transcription, or realtime voice provider descriptors without importing
the full plugin entry. See the typed SDK contract.
Each supplied family is authoritative, including an empty array. An omitted
family, or a plugin without this declaration, retains the existing register()
discovery contract for installed plugins. A malformed, missing, or broken declared
entry fails with a repair diagnostic; it does not fall through to full registration.
Already registered runtime providers remain authoritative, including live broker
and readiness closures.
The entry uses the same plugin-root boundary checks, installed-owner precedence,
prepared metadata generation, and source/built artifact policy as other plugin
surfaces. Repository builds include declared entries and rewrite emitted manifest
paths to the corresponding JavaScript artifacts. Plugin reload owns invalidation;
catalog requests do not poll files for changes.
Configuration validation
- Required-field errors identify every missing field after schema defaults are applied. For dependencies on multiple fields, the error reports the dependency condition without claiming that fields already present are missing.
- Unknown
channels.*keys are errors, unless the channel id is declared by a plugin manifest. If the same id also appears inplugins.allow,plugins.entries, orplugins.installs(a plugin that is referenced but not currently discoverable), OpenClaw downgrades this to a warning instead. plugins.entries.<id>,plugins.allow, andplugins.denyreferencing unknown plugin ids are warnings (“stale config entry ignored”), not errors, so upgrades and removed/renamed plugins do not block gateway startup. An exact{ enabled: false }plugin entry is an intentional uninstall marker, so validation and Doctor keep it without a stale-config warning.plugins.slots.memoryreferencing an unknown plugin id is an error, except for the knownmemory-lancedbofficial external plugin, which warns instead.- If a plugin is installed but has a broken or missing manifest or schema, validation fails and Doctor reports the plugin error.
- If plugin config exists but the plugin is disabled, the config is kept and a warning is surfaced in Doctor + logs.
plugins.* schema.
Notes
- The manifest is required for native OpenClaw plugins, including local filesystem loads. Runtime still loads the plugin module separately; the manifest is only for discovery + validation.
- Native manifests are parsed with JSON5, so comments, trailing commas, and unquoted keys are accepted as long as the final value is still an object.
- Only documented manifest fields are read by the manifest loader. Avoid custom top-level keys.
channels,providers,cliBackends, andskillscan all be omitted when a plugin does not need them.providerCatalogEntrymust stay lightweight and should not import broad runtime code; use it for static provider catalog metadata or narrow discovery descriptors, not request-time execution.- Exclusive plugin kinds are selected through
plugins.slots.*:kind: "memory"viaplugins.slots.memory(defaultmemory-core),kind: "context-engine"viaplugins.slots.contextEngine(defaultlegacy). - Declare exclusive plugin kind in this manifest. Runtime-entry
OpenClawPluginDefinition.kindis deprecated and remains only as a compatibility fallback for older plugins. - Env-var metadata in
setup.providers[].envVarsis declarative only. 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 requires provider code, see 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
Getting started with plugins.
Plugin architecture
Internal architecture and capability model.
SDK overview
Plugin SDK reference and subpath imports.