---
summary: "Plugin compatibility contracts, deprecation metadata, and migration expectations"
title: "Plugin compatibility"
read_when:
  - You maintain an OpenClaw plugin
  - You see a plugin compatibility warning
  - You are planning a plugin SDK or manifest migration
---

OpenClaw keeps older plugin contracts wired through named compatibility
adapters before removing them. This protects existing bundled and external
plugins while the SDK, manifest, setup, config, and agent runtime contracts
evolve.

## Compatibility registry

Plugin compatibility contracts are tracked in the core registry at
`src/plugins/compat/registry.ts`.

Each record has:

- a stable compatibility code
- status: `active`, `deprecated`, `removal-pending`, or `removed`
- owner: SDK, config, setup, channel, provider, plugin execution, agent runtime,
  or core
- introduction and deprecation dates when applicable
- replacement guidance
- docs, diagnostics, and tests that cover the old and new behavior

The registry is the source for maintainer planning and future plugin inspector
checks. If a plugin-facing behavior changes, add or update the compatibility
record in the same change that adds the adapter.

Doctor repair and migration compatibility is tracked separately at
`src/commands/doctor/shared/deprecation-compat.ts`. Those records cover old
config shapes, install-ledger layouts, and repair shims that may need to stay
available after the runtime compatibility path is removed.

Release sweeps should check both registries. Do not delete a doctor migration
just because the matching runtime or config compatibility record expired; first
verify there is no supported upgrade path that still needs the repair. Also
revalidate each replacement annotation during release planning because plugin
ownership and config footprint can change as providers and channels move out of
core.

## Plugin inspector package

The plugin inspector should live outside the core OpenClaw repo as a separate
package/repository backed by the versioned compatibility and manifest
contracts.

The day-one CLI should be:

```sh
openclaw-plugin-inspector ./my-plugin
```

It should emit:

- manifest/schema validation
- the contract compatibility version being checked
- install/source metadata checks
- cold-path import checks
- deprecation and compatibility warnings

Use `--json` for stable machine-readable output in CI annotations. OpenClaw
core should expose contracts and fixtures the inspector can consume, but should
not publish the inspector binary from the main `openclaw` package.

### Maintainer acceptance lane

Use Crabbox-backed Blacksmith Testbox for the installable-package acceptance
lane when validating the external inspector against OpenClaw plugin packages.
Run it from a clean OpenClaw checkout after the package is built:

```sh
pnpm crabbox:run -- --provider blacksmith-testbox --timing-json --shell -- "pnpm install && pnpm build && npm exec --yes @openclaw/plugin-inspector@0.1.0 -- ./extensions/telegram --json"
pnpm crabbox:run -- --provider blacksmith-testbox --timing-json --shell -- "npm exec --yes @openclaw/plugin-inspector@0.1.0 -- ./extensions/discord --json"
pnpm crabbox:run -- --provider blacksmith-testbox --timing-json --shell -- "npm exec --yes @openclaw/plugin-inspector@0.1.0 -- <clawhub-plugin-dir> --json"
```

Keep this lane opt-in for maintainers because it installs an external npm
package and may inspect plugin packages cloned outside the repo. The local repo
guards cover the SDK export map, compatibility registry metadata, deprecated
SDK-import burn-down, and bundled extension import boundaries; Testbox inspector
proof covers the package as external plugin authors consume it.

## Deprecation policy

OpenClaw should not remove a documented plugin contract in the same release
that introduces its replacement.

The migration sequence is:

1. Add the new contract.
2. Keep the old behavior wired through a named compatibility adapter.
3. Emit diagnostics or warnings when plugin authors can act.
4. Document the replacement and timeline.
5. Test both old and new paths.
6. Wait through the announced migration window.
7. Remove only with explicit breaking-release approval.

Deprecated records must include a warning start date, replacement, docs link,
and final removal date no more than three months after the warning starts. Do
not add a deprecated compatibility path with an open-ended removal window unless
maintainers explicitly decide it is permanent compatibility and mark it `active`
instead.

## Current compatibility areas

Current compatibility records include:

- legacy broad SDK imports such as `openclaw/plugin-sdk/compat`
- legacy hook-only plugin shapes and `before_agent_start`
- legacy `api.on("deactivate", ...)` cleanup hook names while plugins migrate to
  `gateway_stop`
- legacy `activate(api)` plugin entrypoints while plugins migrate to
  `register(api)`
- legacy SDK aliases such as `openclaw/extension-api`,
  `openclaw/plugin-sdk/channel-runtime`, `openclaw/plugin-sdk/command-auth`
  status builders, `openclaw/plugin-sdk/test-utils` (replaced by focused
  `openclaw/plugin-sdk/*` test subpaths), and the `ClawdbotConfig` /
  `OpenClawSchemaType` type aliases
- bundled plugin allowlist and enablement behavior
- legacy provider/channel env-var manifest metadata
- legacy provider plugin hooks and type aliases while providers move to
  explicit catalog, auth, thinking, replay, and transport hooks
- legacy runtime aliases such as `api.runtime.taskFlow`,
  `api.runtime.subagent.getSession`, `api.runtime.stt`, and deprecated
  `api.runtime.config.loadConfig()` / `api.runtime.config.writeConfigFile(...)`
- WhatsApp `WebInboundMessage` flat callback fields such as `body`, `chatId`,
  `reply(...)`, and `mediaPath` while callback consumers migrate to the nested
  `WebInboundCallbackMessage` `event`, `payload`, `quote`, `group`, and
  `platform` contexts
- WhatsApp `WebInboundMessage` top-level admission fields such as `from`,
  `conversationId`, `accountId`, `accessControlPassed`, and `chatType` while
  callback consumers migrate to the `admission` envelope
- legacy memory-plugin split registration while memory plugins move to
  `registerMemoryCapability`
- legacy memory-specific embedding provider registration while embedding
  providers move to `api.registerEmbeddingProvider(...)` and
  `contracts.embeddingProviders`
- legacy channel SDK helpers for native message schemas, mention gating,
  inbound envelope formatting, and approval capability nesting
- legacy channel route key and comparable-target helper aliases while plugins
  move to `openclaw/plugin-sdk/channel-route`
- activation hints that are being replaced by manifest contribution ownership
- `setup-api` runtime fallback while setup descriptors move to cold
  `setup.requiresRuntime: false` metadata
- provider `discovery` hooks while provider catalog hooks move to
  `catalog.run(...)`
- channel `showConfigured` / `showInSetup` metadata while channel packages move
  to `openclaw.channel.exposure`
- legacy runtime-policy config keys while doctor migrates operators to
  `agentRuntime`
- generated bundled channel config metadata fallback while registry-first
  `channelConfigs` metadata lands
- persisted plugin registry disable and install-migration env flags while
  repair flows migrate operators to `openclaw plugins registry --refresh` and
  `openclaw doctor --fix`
- legacy plugin-owned web search, web fetch, and x_search config paths while
  doctor migrates them to `plugins.entries.<plugin>.config`
- legacy `plugins.installs` authored config and bundled plugin load-path
  aliases while install metadata moves into the state-managed plugin ledger

New plugin code should prefer the replacement listed in the registry and in the
specific migration guide. Existing plugins can keep using a compatibility path
until the docs, diagnostics, and release notes announce a removal window.

### WhatsApp Inbound Callback Flat Aliases

WhatsApp runtime callbacks deliver `WebInboundMessage`: the canonical nested
`event`, `payload`, `quote`, `group`, and `platform` contexts plus deprecated
flat aliases for the shipped callback fields. New callback code should read the
nested contexts. Code that constructs clean nested callback messages can use
`WebInboundCallbackMessage`; compatibility listeners that still inject old flat
test or plugin messages should use `LegacyFlatWebInboundMessage` or
`WebInboundMessageInput`.

The flat aliases remain available until **2026-08-30**. That removal window
applies only to flat alias access; the nested callback shape is the canonical
runtime contract. The TypeScript `@deprecated` annotations on each flat alias
name its exact nested replacement. Common examples:

- `id`, `timestamp`, and `isBatched` move under `event`.
- `body`, `mediaPath`, `mediaType`, `mediaFileName`, `mediaUrl`, `location`, and
  `untrustedStructuredContext` move under `payload`.
- `to`, `chatId`, sender/self fields, `sendComposing`, `reply(...)`, and
  `sendMedia(...)` move under `platform`.
- `replyTo*` fields move under `quote`, and group subject/participant/mention
  fields move under `group`.

`payload.untrustedStructuredContext` is extracted from inbound provider payloads.
Plugins should inspect the `label`, `source`, and `type` before treating its
`payload` as authoritative.

### WhatsApp Inbound Admission Fields

Accepted WhatsApp callback messages now carry `admission`, a public-safe
envelope for the access-control decision that admitted the message. New callback
code should read admission facts from `msg.admission` instead of the older
top-level admission fields.

The top-level fields remain available until **2026-08-30**. The TypeScript
`@deprecated` annotations name each replacement:

- `from` and `conversationId` move to `admission.conversation.id`.
- `accountId` moves to `admission.accountId`.
- `accessControlPassed` is a derived compatibility view of
  `admission.ingress.decision === "allow"`; on messages that already carry
  `admission`, writing the legacy boolean does not rewrite the ingress graph.
- `chatType` moves to `admission.conversation.kind`.

## Release notes

Release notes should include upcoming plugin deprecations with target dates and
links to migration docs. That warning needs to happen before a compatibility
path moves to `removal-pending` or `removed`.
