Skip to main content
defineToolPlugin builds a plugin that only adds agent-callable tools: no channel, model provider, hook, service, or setup backend. It generates the manifest metadata OpenClaw needs to discover tools without loading plugin runtime code. For provider, channel, hook, service, or mixed-capability plugins, start with Building plugins, Channel Plugins, or Provider Plugins instead.

Requirements

  • Node 22.22.3+, Node 24.15+, or Node 25.9+.
  • TypeScript ESM package output.
  • typebox in dependencies (not just devDependencies - the generated plugin imports it at runtime).
  • openclaw >=2026.5.17, the first version that exports openclaw/plugin-sdk/tool-plugin.
  • A package root that ships dist/, openclaw.plugin.json, and package.json.

Quickstart

plugins init scaffolds: npm run plugin:build runs npm run build (tsc) then openclaw plugins build --entry ./dist/index.js. npm run plugin:validate rebuilds and runs openclaw plugins validate --entry ./dist/index.js. Successful validation prints:
openclaw plugins init <id> options:

Write a tool

defineToolPlugin takes plugin identity, an optional config schema, and a static list of tools. Parameter and config types are inferred from the TypeBox schemas.
Tool names are the stable API. Pick names that are unique, lowercase, and specific enough to avoid collisions with core tools or other plugins.

Optional and factory tools

Set optional: true when users should explicitly allowlist the tool before it is sent to a model. openclaw plugins build writes the matching toolMetadata.<tool>.optional manifest entry, so OpenClaw can see that the tool is optional without loading plugin runtime code.
Use factory when a tool needs the runtime tool context before it can be created - to opt out for a specific run, inspect sandbox state, or bind runtime helpers. Metadata stays static even though the concrete tool is built at runtime.
Factories still declare a fixed tool name up front. Use definePluginEntry directly when the plugin computes tool names dynamically or combines tools with hooks, services, providers, or commands.

Return values

defineToolPlugin wraps plain return values into the OpenClaw tool-result format:
  • Return a string when the model should see that exact text.
  • Return a JSON-compatible value when you want the model to see formatted JSON and OpenClaw to keep the original value in details.
Use a factory tool when you need a custom AgentToolResult or want to reuse an existing api.registerTool implementation.

Configuration

configSchema is optional. Omit it and OpenClaw applies a strict empty object schema; the generated manifest still includes configSchema.
With a configSchema, the second execute argument is typed from it:
OpenClaw reads plugin config from the plugin’s entry in the Gateway config. Do not hard-code secrets in source or docs examples; use config, environment variables, or SecretRefs per the plugin’s security model.

Generated metadata

OpenClaw must read the plugin manifest before importing plugin runtime code. defineToolPlugin exposes static metadata for this, and openclaw plugins build writes it into the package. Rerun the generator after changing plugin id, name, description, config schema, activation, or tool names:
Generated manifest for a one-tool plugin:
contracts.tools is the important discovery contract: it tells OpenClaw which plugin owns each tool without loading every installed plugin’s runtime. A stale manifest means a tool can go missing from discovery, or a registration error gets blamed on the wrong plugin.

Package metadata

openclaw plugins build also aligns package.json to the selected runtime entry:
Ship built JavaScript (./dist/index.js), not a TypeScript source entry. Source entries only work for workspace-local development.

Validate in CI

plugins build --check fails without rewriting files when generated metadata is stale:
plugins validate checks that:
  • openclaw.plugin.json exists and passes the normal manifest loader.
  • The current entry exports defineToolPlugin metadata.
  • Generated manifest fields match the entry metadata.
  • contracts.tools matches the declared tool names.
  • package.json points openclaw.extensions at the selected runtime entry.

Install and inspect locally

From a separate OpenClaw checkout or installed CLI, install the package path:
For a packaged smoke test, pack first and install the tarball:
After installing, restart or reload the Gateway and ask the agent to use the tool. If the tool is not visible, inspect the plugin runtime and the effective tool catalog before changing code (see Troubleshooting).

Publish

Publish through ClawHub once the package is ready. clawhub package publish takes a source: a local folder, a GitHub repo (owner/repo[@ref]), or a tarball URL.
Install with an explicit ClawHub locator:
Bare npm package specs still install from npm during the launch cutover, but ClawHub is the preferred discovery and distribution surface for OpenClaw plugins. See ClawHub publishing for owner scope and release review.

Troubleshooting

plugin entry not found: ./dist/index.js

The selected entry file does not exist. Run npm run build, then rerun openclaw plugins build --entry ./dist/index.js or openclaw plugins validate --entry ./dist/index.js.

plugin entry does not expose defineToolPlugin metadata

The entry did not export a value created by defineToolPlugin. Confirm the module’s default export is the defineToolPlugin(...) result, or pass the correct entry with --entry.

openclaw.plugin.json generated metadata is stale

The manifest no longer matches the entry metadata. Run:
Commit both openclaw.plugin.json and package.json changes.

package.json openclaw.extensions must include ./dist/index.js

The package metadata points at a different runtime entry. Run openclaw plugins build --entry ./dist/index.js so the generator aligns package metadata with the entry you intend to ship.

Cannot find package 'typebox'

The built plugin imports typebox at runtime. Keep it in dependencies, reinstall, rebuild, and rerun validation.

Tool does not appear after install

Check these in order:
  1. openclaw plugins inspect <plugin-id> --runtime
  2. openclaw plugins validate --root <plugin-root> --entry ./dist/index.js
  3. openclaw.plugin.json has contracts.tools with the expected tool names.
  4. package.json has openclaw.extensions: ["./dist/index.js"].
  5. The Gateway was restarted or reloaded after installing the plugin.

See also