defineToolPlugin, definePluginEntry,
defineChannelPluginEntry, defineSetupPluginEntry.
All plugin APIs are experimental,
including these entry helpers. Pin and test the OpenClaw host versions your
plugin supports.
Package entries
Installed plugins pointpackage.json openclaw fields at both source and
built entries:
extensionsandsetupEntryare source entries, used for workspace and git checkout development.runtimeExtensionsandruntimeSetupEntryselect the built entries instead of the corresponding source entries.runtimeExtensions, when present, must matchextensionsin array length (entries pair positionally).runtimeSetupEntryrequiressetupEntry.- If a
runtimeExtensions/runtimeSetupEntryartifact is declared but missing, installation fails and discovery reports a packaging error for that entry; OpenClaw does not silently fall back to source. - Without an explicit runtime entry, package discovery through
plugins.load.pathsor global roots looks for matching JavaScript peers underdist/first, then beside the TypeScript source entry. Forsrc/entries, it checks both flatteneddist/output and output retainingdist/src/. At each location,.mtsprefers.mjsand.ctsprefers.cjs;.tsand.tsxtry.js,.mjs, then.cjs. Installation, discovery, setup, runtime loading, and published-package verification use the same candidate order. - A
plugins.load.pathsentry that resolves inside the host’s own bundled plugin tree is discovered as that bundled plugin, so it keeps the bundled entry point and bundled provenance whether or not compiled output exists beside the source. Selecting a bundled plugin’s own path never reclassifies it. - Package installation and managed installed-package discovery require compiled output for TypeScript extension and setup entries. Missing compiled output is a packaging error, not a reason to fall back to TypeScript.
- Trusted local/source development paths can use TypeScript when no runtime entry is declared. These include workspace plugins, explicit local load paths, untracked local plugin directories, and linked source checkouts. Workspace discovery keeps the source entry rather than inferring built peers.
- All entry paths must stay inside the plugin package directory. Runtime
entries and inferred built-JS peers do not make an escaping
extensionsorsetupEntrysource path valid.
defineToolPlugin
Import: openclaw/plugin-sdk/tool-plugin
For plugins that only add agent tools. Keeps the source small, infers config
and tool-parameter types from TypeBox schemas, wraps plain return values in
the OpenClaw tool-result format, and exposes static metadata that
openclaw plugins build writes into the plugin manifest (contracts.tools,
configSchema).
configSchemais optional; omitting it uses a strict empty object schema (the generated manifest still includesconfigSchema).executereturns a plain string or JSON-serializable value; the helper wraps it as a text tool result withdetailsset to the original (unstringified) return value.outputSchemaoptionally describes that originaldetailsvalue for Code Mode and Tool Search. Catalog calls reject an invalid schema before execution and validate the final value before returning it.- For custom tool results,
openclaw/plugin-sdk/tool-resultsexportstextResultandjsonResult. - Tool names are static, so
openclaw plugins buildderivescontracts.toolsfrom the declared tools without hand-duplicated names. - Runtime loading stays strict: installed plugins still need
openclaw.plugin.jsonandpackage.jsonopenclaw.extensions. OpenClaw never executes plugin code to infer missing manifest data.
definePluginEntry
Import: openclaw/plugin-sdk/plugin-entry
For provider plugins, advanced tool plugins, hook plugins, and anything that
is not a messaging channel.
-
idmust match youropenclaw.plugin.jsonmanifest. -
External session catalogs use
openclaw/plugin-sdk/session-catalogand register aSessionCatalogProviderwithapi.registerSessionCatalog(...). Required provider fields areid,label,list, andread; optional hooks areresolveCreateSession,continueSession,copyToGatewaySession,checkUpstreamActivity,archive,openTerminal, andstartTerminalSession. Core owns thesessions.catalog.*Gateway methods; providers return host, session, transcript, and terminal-plan projections without registering RPCs. A list provider should call the optionalonHost(host)callback as each host settles; the returned host array remains required as the final compatibility snapshot. If a host can finish afterlistreturns a fail-soft snapshot, register its bounded completion with the optionalwaitUntil(completion: Promise<void>)hook beforelistsettles. Include host mapping and theonHostcall in that promise. UsepublishSessionCatalogHost({ onHost, waitUntil }, pendingHost)from the same SDK entry point to publish the host and register the complete callback chain. Registration afterlistsettles is rejected. Providers that do not register completion work finish publishing when theirlistsettles. The optionalsignal: AbortSignalbelongs to the catalog operation or provider lifetime. Pass it to cancellable work, including the top-levelsignalfield ofapi.runtime.nodes.invoke(...). A requesting client disconnect only removes that client’s subscription; it does not cancel shared discovery. Retaining completion does not extend native invocation or fail-soft response deadlines, grant new authority, or permit starting work after the owner retires. Providers remain responsible for bounded work that settles after cancellation. KeeponHost,waitUntil, andsignalseparate from validated catalog query objects and node command payloads. The request-ownedsessionEntriessnapshot andlistNodeshook still must not be retained pastlist; prepare any facts needed by late host mapping before returning. Transcript items may include asenderwith a qualifiedSessionParticipantidentity and optional display label or avatar. Supply only source-known attribution; the viewer and the session adopter are not transcript authors. Core resolves profile identities against current profile data, including merges. User items without attribution display as User. A Gateway-hosted catalog may setaudience: "gateway-operators"when every authenticated operator withoperator.readmay view its rows. Such a provider may implementcopyToGatewaySession(...)to return a bounded display name and optional preferred model for an independent Gateway-owned continuation. Core owns operator and agent authorization, session creation, model readiness and policy checks, rollback, and untrusted-content wrapping. The provider supplies transcript text throughread(...); it must not write the destination session. Native source titles are presentation, not unique session labels. When adopting a new source, pass its title asdisplayNameto the owner-authorized session creator; the host bounds and stores that snapshot with the new row. Keep source identity independent of naming, preserve existing labels and snapshots on reuse or recovery, and do not resync native renames. A provider may declare one readable transcript route withshareRoute. This is a closed contract, not a free-form routing hint:The provider must return lowercase hexadecimalthreadIdvalues of exactly 32 characters on the declared host. Whenlist(...)receives asearchvalue that is a valid 12-32 character prefix, that host must return only rows whosethreadIdstarts with the prefix. Return every match up to the requested limit and setnextCursorwhen more may exist. The Control UI resolves only one result with no next page; multiple rows ornextCursorare explicitly ambiguous and never select the first row. Named share links use/<routeSegment>/<title-slug>-<id-prefix>with the same bounded slug as regular session links. Return the title in the catalog row’sname; the Control UI uses it to refresh the decorative slug. Only the id suffix selects the transcript. Bare-id and stale-title links remain valid, and titles never resolve an ambiguous id.routeSegmentmust not use the first segment of a built-in Control UI route or alias, and it must be unique across active session catalogs. Invalid, unsupported, reserved, or multiply owned descriptors fail closed; catalog sessions remain available through the generic/chat/<agent>?catalog=...&host=...&thread=...URL. The shared session URL contract owns the built-in reservation decision: its share-path builder returnsnullfor reserved segments, and the Gateway omits reserved descriptors before publishing catalogs. Keep one plugin-owned descriptor constant and reuse it for registration, prefix lookup, and URL generation so those obligations cannot drift. CLI-backed catalogs that expose the same local-plus-paired-node shape can usecreateSessionCatalogFamily(...). The family composer owns canonical cursor validation, node payload validation, host projection, adopted-session projection, per-host publication, read routing, single-flight continuation per resolved agent and source, and terminal plan routing. Different agents do not share in-flight adoption results; adopted-source lookup keys remain host/thread pairs. The provider must supply its local store reads, identifiers and commands, error text, capability projection, continuation availability and persistence operations, upstream-activity check, and terminal executable/arguments. There are no default continuation, capability-mutation, or terminal authorities. UsecreateSessionCatalogNodeHostBindings(...)to build the matching list/read/terminal node commands and terminal-only invoke policy from those explicit provider inputs. The same entrypoint exportssessionCatalogPaging, which groups the bounded list/read parameter parsers, canonical base64url cursor codec, and bounded UTF-8 transcript pager. Providers pass their own identifier pattern and validation messages intoparseReadParams(...)andparseListParams(...).resolveCreateSession({ agentId })must return a config-derived model/runtime target before OpenClaw advertises model-chat creation. Native terminal readiness is independent of this target. Useapi.runtime.agent.resolveSessionCatalogCreateTarget(...)to apply the host’s runtime and model-allowlist policy instead of duplicating it.startTerminalSessionadvertisescapabilities.startTerminal: trueindependently of model-chat creation. ReturncanStartTerminal: trueon each eligible host from the ordinary cataloglistcallback, including empty hosts. Publish the same flag in progressiveonHostframes and final results; explicitly returnfalsewhen readiness changes. A failed transcript listing does not revoke an otherwise available CLI. Node hosts require their exact connected, invocable fresh-start command; start-only nodes must not invoke a missing list command. Preserve local source IDs and process-home isolation. The shippedcreateSession.startTerminalfield remains model-chat metadata; new terminal callers use the independent capability and raw catalog hosts.startTerminalSession({ agentId, cwd, initialMessage?, nodeId?, hostId? })creates a fresh CLI terminal plan. Return either a local plan (kind: "local",argv, and the exactcwd, plus optionalenv,pathEnv, andtitle) or a paired-node plan (kind: "node",nodeId,command,paramsJSON, and the exactcwd). Thesessions.catalog.startTerminalRPC requiresoperator.adminplusgateway.cliAgents.enabledandgateway.terminal.enabled. The caller provisionscwd; the Gateway requires an existing absolute local directory, rejects a changed plan cwd or host, and applies the normal agent-sandbox, node-pairing, deadline, and connection-ownership checks before opening the PTY.hostIdcarries the selected local source;nodeIdidentifies a node. Initial prompts are bounded to 16,384 characters and cwd to 4,096 characters (4,096 UTF-8 bytes on nodes). Fresh node commands usedecodeNodePtyStartParamsfromnode-hostandrunNodePtyCommand({ ..., requiredCwd: true }, io)to require an existing absolute node directory, including a recheck immediately before spawning. Resume retains its existing cwd fallback contract. Node payloads must not accept executable, argv, environment, credentials, or a Gateway agent as native account selection. The terminal manager retains the native title and actual connection/agent owner across attach and reconnect. Clients advertiseterminal-session-metadatato receive attach title/owner and list titles; older closed response shapes stay unchanged. -
kindis deprecated: declare an exclusive slot ("memory"or"context-engine") in theopenclaw.plugin.jsonmanifestkindfield instead. Runtime-entrykindremains only as a compatibility fallback for older plugins. -
configSchemacan be a function for lazy evaluation. OpenClaw resolves and memoizes the schema on first access, so expensive schema builders only run once. -
A
nodeHostCommandsdescriptor can defineisAvailable({ config, env }). Returningfalseomits that command and its capability from the headless node’s Gateway declaration. OpenClaw evaluates it against the node-local startup config; command handlers should still validate availability when invoked.
Computer Use providers
Import:openclaw/plugin-sdk/computer-use
Node-local Computer Use plugins register one provider through
registerComputerUseProvider(api, provider). The helper owns the
screen.snapshot and dangerous computer.act command registrations and the
matching Gateway invoke policy; the provider owns availability, execution,
serialization, frame state, driver lifecycle, and cleanup.
The same entry point exports the canonical TypeBox schemas, static types, and
compiled validators for the two command payloads and the snapshot result. A
node host accepts one provider for the command pair; registering another
provider conflicts with the existing command registration instead of creating
a fallback stack.
defineChannelPluginEntry
Import: openclaw/plugin-sdk/channel-core
Wraps definePluginEntry with channel-specific wiring: it automatically
calls api.registerChannel({ plugin }), exposes an optional root-help CLI
metadata seam, and gates capability and full-runtime callbacks on registration
mode.
Callbacks run per registration mode (full table under
Registration mode):
setRuntimeruns in every mode except"cli-metadata"and"tool-discovery". Store the runtime reference here, typically viacreatePluginRuntimeStore.registerCliMetadataruns for"cli-metadata","discovery", and"full". Use it as the canonical place for channel-owned CLI descriptors so root help stays non-activating, discovery snapshots include static command metadata, and normal CLI registration stays compatible with full plugin loads.registerFullruns only for"full"and"tool-discovery". For"tool-discovery"it runs instead of channel registration: OpenClaw skipsregisterChannel/setRuntimeentirely and calls the full-runtime callback followed by the capability callback. Keep tool registration inregisterFulland capability providers inregisterCapabilities.registerCapabilitiesruns for"discovery","full", and"tool-discovery". Register inert advertised providers here so read-only capability discovery can find them without starting sockets, clients, workers, or services.- Discovery registration is non-activating, not import-free: OpenClaw may
evaluate the trusted plugin entry and channel plugin module to build the
snapshot. Keep top-level imports side-effect-free and put sockets,
clients, workers, and services behind
"full"-only paths. - Like
definePluginEntry,configSchemacan be a lazy factory; OpenClaw memoizes the resolved schema on first access.
- Use
api.registerCli(..., { descriptors: [...] })for plugin-owned root CLI commands you want lazy-loaded without disappearing from the root CLI parse tree. Descriptor names must match letters, numbers, hyphen, and underscore, starting with a letter or number; OpenClaw rejects other shapes and strips terminal control sequences from descriptions before rendering help. Cover every top-level command root the registrar exposes, and declare the same name, description, and subcommand marker in the plugin manifest’scliCommandsfield so root help does not import plugin code.commandsalone stays on the eager compatibility path. - Root descriptors may define a synchronous, pure
machineOutput({ argv, stdoutIsTTY })resolver for JSON, JSONL, or other machine-readable stdout modes that are not selected solely by--json. Parse command tokens withgetRootOptionAwareCommandPathfromopenclaw/plugin-sdk/cli-argv. Keep the descriptor in a lightweight plugin-local module and reuse it from bothcli-metadata.tsand full registration; do not import runtime barrels to construct metadata. Meeting runtime shells accept that descriptor throughcli.descriptor. Nested descriptors do not exposemachineOutput. - Use
api.registerNodeCliFeature(...)for paired-node feature commands so they land underopenclaw nodes(equivalent toregisterCli(registrar, { parentPath: ["nodes"], ... })). - For other nested plugin commands, add
parentPathand register commands on theprogramobject passed to the registrar; OpenClaw resolves it to the parent command before calling the plugin. - For channel plugins, register CLI descriptors from
registerCliMetadataand keepregisterFullfocused on runtime-only work. - If
registerFullalso registers gateway RPC methods, keep them on a plugin-specific prefix. Reserved core admin namespaces (config.*,exec.approvals.*,wizard.*,update.*) always coerce tooperator.admin.
defineSetupPluginEntry
Import: openclaw/plugin-sdk/channel-core
For the lightweight setup-entry.ts file. Returns just { plugin } with no
runtime or CLI wiring.
defineSetupPluginEntry(...) with the narrow setup helper families:
Keep heavy SDKs, CLI registration, and long-lived runtime services in the
full entry.
Bundled workspace channels that split setup and runtime surfaces can use
defineBundledChannelSetupEntry(...) from
openclaw/plugin-sdk/channel-entry-contract instead. It lets the setup
entry keep setup-safe plugin/secrets exports while still exposing a runtime
setter:
registerSetupRuntime runs only for "setup-runtime" loads; keep it
limited to config-only routes or methods required by that setup flow.
Registration mode
api.registrationMode tells your plugin how it was loaded:
In
"cli-metadata" and "setup-only" modes, accessing a runtime capability throws an error naming the plugin and mode. Defer runtime access out of register() or declare root commands in the manifest’s cliCommands so CLI metadata can be collected without executing the plugin.
defineChannelPluginEntry handles this split automatically. If you use
definePluginEntry directly for a channel, check mode yourself and remember
"tool-discovery" skips channel registration:
plugin.<plugin-id>.changed. Event names are one
lowercase segment, payloads must be bounded JSON, and the scope must be
operator.read, operator.write, or operator.admin. The emitter exists only
for the service lifetime and is revoked after stop or failed start. Prefer
version or invalidation payloads over full records so authorized clients reread
canonical state through the plugin’s scoped Gateway methods.
Discovery mode builds a non-activating registry snapshot. It may still
evaluate the plugin entry and the channel plugin object so OpenClaw can
register channel capabilities and static CLI descriptors. Treat module
evaluation in discovery as trusted but lightweight: no network clients,
subprocesses, listeners, database connections, background workers,
credential reads, or other live runtime side effects at top level.
Treat "setup-runtime" as the window where setup-only startup surfaces must
exist without re-entering the full bundled channel runtime. Good fits are
channel registration, setup-safe HTTP routes, setup-safe gateway methods,
and delegated setup helpers. Heavy background services, CLI registrars, and
provider/client SDK bootstraps still belong in "full".
Plugin shapes
OpenClaw classifies loaded plugins by their registration behavior:
Use
openclaw plugins inspect <id> to see a plugin’s shape.
Related
- SDK Overview - registration API and subpath reference
- Runtime Helpers -
api.runtimeandcreatePluginRuntimeStore - Setup and Config - manifest and setup entry loading
- Channel Plugins - building the
ChannelPluginobject - Provider Plugins - provider registration and hooks