Skip to main content
OpenClaw replaced a broad backwards-compatibility layer with a modern plugin architecture built from small, focused imports. If your plugin predates that change, this guide gets it onto the current contracts.

What changed

Two wide-open import surfaces used to let plugins reach almost anything from a single entry point:
  • openclaw/plugin-sdk/compat - re-exported dozens of helpers to keep older hook-based plugins working while the new architecture was built.
  • openclaw/plugin-sdk/infra-runtime - a broad barrel mixing system events, heartbeat state, delivery queues, fetch/proxy helpers, file helpers, approval types, and unrelated utilities.
  • openclaw/plugin-sdk/config-runtime - a broad config barrel still carrying deprecated direct load/write helpers during the migration window.
  • openclaw/extension-api - a bridge giving plugins direct access to host-side helpers like the embedded agent runner.
  • api.registerEmbeddedExtensionFactory(...) - a removed embedded-runner-only hook that observed embedded-runner events such as tool_result. Use agent tool-result middleware instead (see Migrate embedded tool-result extensions to middleware).
These surfaces are deprecated: they still work, but new plugins must not use them, and existing plugins should migrate before the next major release removes them. registerEmbeddedExtensionFactory has already been removed; legacy registrations no longer load.
The backwards-compatibility layer will be removed in a future major release. Plugins still importing from these surfaces will break when that happens.
OpenClaw does not remove or reinterpret documented plugin behavior in the same change that introduces a replacement. Breaking contract changes go through a compatibility adapter, diagnostics, docs, and a deprecation window first. That applies to SDK imports, manifest fields, setup APIs, hooks, and runtime registration behavior.

Why

  • Slow startup - importing one helper loaded dozens of unrelated modules.
  • Circular dependencies - broad re-exports made import cycles easy to create.
  • Unclear API surface - no way to tell stable exports from internal ones.
Each openclaw/plugin-sdk/<subpath> is now a small, self-contained module with a documented contract. Legacy provider convenience seams for bundled channels are gone too - channel-branded helper shortcuts were private mono-repo conveniences, not stable plugin contracts. Use narrow generic SDK subpaths instead. Inside the bundled plugin workspace, keep provider-owned helpers in that plugin’s own api.ts or runtime-api.ts:
  • Anthropic keeps Claude-specific stream helpers in its own api.ts / contract-api.ts seam.
  • OpenAI keeps provider builders, default-model helpers, and realtime provider builders in its own api.ts.
  • OpenRouter keeps provider builder and onboarding/config helpers in its own api.ts.

Compatibility policy

External-plugin compatibility work follows this order:
  1. Add the new contract.
  2. Keep the old behavior wired through a compatibility adapter.
  3. Emit a diagnostic or warning naming the old path and replacement.
  4. Cover both paths in tests.
  5. Document the deprecation and migration path.
  6. Remove only after the announced migration window, usually in a major release.
If a manifest field is still accepted, keep using it until docs and diagnostics say otherwise. New code should prefer the documented replacement; existing plugins should not break during ordinary minor releases. Audit the current migration queue with pnpm plugins:boundary-report: pnpm plugins:boundary-report:ci runs with all three fail flags. Each compatibility record has an explicit removeAfter date (not a vague “next major release”) - the report groups deprecated records by that date, counts local code/doc references, surfaces cross-owner reserved SDK imports, and summarizes the private memory-host SDK bridge. Reserved SDK subpaths must have tracked owner usage; unused reserved exports should be removed from the public SDK.

How to migrate

1

Migrate runtime config load/write helpers

Bundled plugins should stop calling api.runtime.config.loadConfig() and api.runtime.config.writeConfigFile(...) directly. Prefer config already passed into the active call path. Long-lived handlers that need the current process snapshot can use api.runtime.config.current(). Long-lived agent tools should read ctx.getRuntimeConfig() inside execute so a tool created before a config write still sees the refreshed config.Config writes go through the transactional helper with an explicit after-write policy:
Use afterWrite: { mode: "restart", reason: "..." } when the change needs a clean gateway restart, and afterWrite: { mode: "none", reason: "..." } only when the caller owns the follow-up and deliberately suppresses the reload planner. Mutation results include a typed followUp summary for tests and logging; the gateway remains responsible for applying or scheduling the restart.loadConfig and writeConfigFile remain as deprecated compatibility helpers for external plugins and warn once with the runtime-config-load-write compatibility code. Bundled plugins and repo runtime code are guarded by pnpm check:deprecated-api-usage and pnpm check:no-runtime-action-load-config: new production plugin usage fails outright, direct config writes fail, gateway server methods must use the request runtime snapshot, runtime channel send/action/client helpers must receive config from their boundary, and long-lived runtime modules allow zero ambient loadConfig() calls.New plugin code should avoid the broad openclaw/plugin-sdk/config-runtime barrel. Use the narrow subpath for the job:Bundled plugins and their tests are scanner-guarded against the broad barrel so imports and mocks stay local to the behavior they need. The barrel still exists for external compatibility, but new code should not depend on it.
2

Migrate embedded tool-result extensions to middleware

Bundled plugins must replace embedded-runner-only api.registerEmbeddedExtensionFactory(...) tool-result handlers with runtime-neutral middleware:
Update the plugin manifest at the same time:
Installed plugins can also register tool-result middleware when explicitly enabled and every targeted runtime is declared in contracts.agentToolResultMiddleware. Undeclared installed middleware registrations are rejected.
3

Migrate approval-native handlers to capability facts

Approval-capable channel plugins expose native approval behavior through approvalCapability.nativeRuntime plus the shared runtime-context registry:
  • Replace approvalCapability.handler.loadRuntime(...) with approvalCapability.nativeRuntime.
  • Move approval-specific auth/delivery off legacy plugin.auth / plugin.approvals wiring and onto approvalCapability.
  • ChannelPlugin.approvals has been removed from the public channel-plugin contract; move delivery/native/render fields onto approvalCapability.
  • plugin.auth remains for channel login/logout flows only; core no longer reads approval auth hooks there.
  • Register channel-owned runtime objects (clients, tokens, Bolt apps) through openclaw/plugin-sdk/channel-runtime-context.
  • Do not send plugin-owned reroute notices from native approval handlers; core owns routed-elsewhere notices from actual delivery results.
  • When passing channelRuntime into createChannelManager(...), provide a real createPluginRuntime().channel surface - partial stubs are rejected.
See Channel Plugins for the current approval capability layout.
4

Audit Windows wrapper fallback behavior

If your plugin uses openclaw/plugin-sdk/windows-spawn, unresolved Windows .cmd/.bat wrappers now fail closed unless you explicitly pass allowShellFallback: true:
If your caller does not intentionally rely on shell fallback, do not set allowShellFallback and handle the thrown error instead.
5

Find deprecated imports

6

Replace with focused imports

Each export from the old surface maps to a specific modern import path:
For host-side helpers, use the injected plugin runtime instead of importing directly:
Same pattern for other legacy bridge helpers:
7

Replace broad infra-runtime imports

openclaw/plugin-sdk/infra-runtime still exists for external compatibility, but new code should import the focused surface it actually needs:Bundled plugins are scanner-guarded against infra-runtime, so repo code cannot regress to the broad barrel.
8

Migrate channel route helpers

New channel route code uses openclaw/plugin-sdk/channel-route. The older route-key and comparable-target names remain as compatibility aliases:The modern route helpers normalize { channel, to, accountId, threadId } consistently across native approvals, reply suppression, inbound dedupe, cron delivery, and session routing.Do not add new uses of ChannelMessagingAdapter.parseExplicitTarget, the parser-backed loaded-route helpers (parseExplicitTargetForLoadedChannel, resolveRouteTargetForLoadedChannel), or resolveChannelRouteTargetWithParser(...) from plugin-sdk/channel-route - those are deprecated and remain only for older plugins. New channel plugins should use messaging.targetResolver.resolveTarget(...) for target-id normalization and directory-miss fallback, messaging.inferTargetChatType(...) when core needs an early peer kind, and messaging.resolveOutboundSessionRoute(...) for provider-native session and thread identity.
9

Build and test

Import path reference

This table is the common migration subset, not the full SDK surface. The compiler entrypoint inventory lives in scripts/lib/plugin-sdk-entrypoints.json; package exports are generated from the public subset. Reserved bundled-plugin helper seams have been retired from the public SDK export map except for explicitly documented compatibility facades such as the deprecated plugin-sdk/discord shim retained for external plugins that still import the published @openclaw/discord package directly. Owner-specific helpers live inside the owning plugin package; shared host behavior moves through generic SDK contracts such as plugin-sdk/gateway-runtime, plugin-sdk/security-runtime, and plugin-sdk/plugin-config-runtime. Use the narrowest import that matches the job. If you cannot find an export, check the source at src/plugin-sdk/ or ask maintainers which generic contract should own it.

Removed compatibility surfaces

Private testing barrel

openclaw/plugin-sdk/testing was repo-local and excluded from shipped package artifacts, so it was removed before its 2026-07-28 removeAfter date. Repository tests use focused subpaths such as plugin-sdk/plugin-test-runtime, plugin-sdk/channel-test-helpers, plugin-sdk/channel-target-testing, plugin-sdk/test-env, and plugin-sdk/test-fixtures.

Active deprecations

Narrower deprecations across the plugin SDK, provider contract, runtime surface, and manifest. Each still works today but will be removed in a future major release. Every entry maps the old API to its canonical replacement.
Old (openclaw/plugin-sdk/command-auth): buildCommandsMessage, buildCommandsMessagePaginated, buildHelpMessage.New (openclaw/plugin-sdk/command-status): same signatures, same exports - just imported from the narrower subpath. command-auth re-exports them as compat stubs.
Old: resolveMentionGating(params) and resolveMentionGatingWithBypass(params) from openclaw/plugin-sdk/channel-inbound or openclaw/plugin-sdk/channel-mention-gating.New: resolveInboundMentionDecision({ facts, policy }) - one decision object instead of two split call shapes.Adopted across Discord, iMessage, Matrix, MS Teams, QQBot, Signal, Telegram, WhatsApp, and Zalo. Slack’s own app_mention event model does not use this helper.
openclaw/plugin-sdk/channel-runtime is a compatibility shim for older channel plugins. Do not import it from new code; use openclaw/plugin-sdk/channel-runtime-context for registering runtime objects.channelActions* helpers in openclaw/plugin-sdk/channel-actions are deprecated alongside raw “actions” channel exports. Expose capabilities through the semantic presentation surface instead - channel plugins declare what they render (cards, buttons, selects) rather than which raw action names they accept.
Old: tool() factory from openclaw/plugin-sdk/provider-web-search.New: implement createTool(...) directly on the provider plugin. OpenClaw no longer needs the SDK helper to register the tool wrapper.
Old: api.runtime.channel.reply.formatInboundEnvelope(...) (and the channelEnvelope field on inbound message objects) to build a flat plaintext prompt envelope from inbound channel messages.New: BodyForAgent plus structured user-context blocks. Channel plugins attach routing metadata (thread, topic, reply-to, reactions) as typed fields instead of concatenating them into a prompt string. The formatAgentEnvelope(...) helper is still supported for synthesized assistant-facing envelopes, but inbound plaintext envelopes are on the way out.Affected areas: inbound_claim, message_received, and any custom channel plugin that post-processed the old envelope text.
Old: api.on("deactivate", handler).New: api.on("gateway_stop", handler). Same shutdown cleanup contract; only the hook name changes.
deactivate remains wired as a deprecated compatibility alias until it is removed after 2026-08-16.
Old: api.on("subagent_spawning", handler) returning threadBindingReady or deliveryOrigin.New: let core prepare thread: true subagent bindings through the channel session-binding adapter. Use api.on("subagent_spawned", handler) only for post-launch observation.
subagent_spawning, PluginHookSubagentSpawningEvent, PluginHookSubagentSpawningResult, and SubagentLifecycleHookRunner.runSubagentSpawning(...) remain only as deprecated compatibility surfaces while external plugins migrate, removed after 2026-08-30.
Four discovery type aliases are now thin wrappers over the catalog-era types:Plus the legacy ProviderCapabilities static bag - provider plugins should use explicit provider hooks such as buildReplayPolicy, normalizeToolSchemas, and wrapStreamFn rather than a static object.
Old (three separate hooks on ProviderThinkingPolicy): isBinaryThinking(ctx), supportsXHighThinking(ctx), and resolveDefaultThinkingLevel(ctx).New: a single resolveThinkingProfile(ctx) that returns a ProviderThinkingProfile with the canonical id, optional label, and a ranked level list. OpenClaw downgrades stale stored values by profile rank automatically.The context includes provider, modelId, optional merged reasoning, and optional merged model compat facts. Provider plugins can use those catalog facts to expose a model-specific profile only when the configured request contract supports it.Implement one hook instead of three. The legacy hooks keep working during the deprecation window but are not composed with the profile result.
Old: implementing external auth hooks without declaring the provider in the plugin manifest.New: declare contracts.externalAuthProviders in the plugin manifest and implement resolveExternalAuthProfiles(...).
Old manifest field: providerAuthEnvVars: { anthropic: ["ANTHROPIC_API_KEY"] }.New: mirror the same env-var lookup into setup.providers[].envVars on the manifest. This consolidates setup/status env metadata in one place and avoids booting the plugin runtime just to answer env-var lookups.providerAuthEnvVars remains supported through a compatibility adapter until the deprecation window closes.
Old: three separate calls - api.registerMemoryPromptSection(...), api.registerMemoryFlushPlan(...), api.registerMemoryRuntime(...).New: one call on the memory-state API - registerMemoryCapability(pluginId, { promptBuilder, flushPlanResolver, runtime }).Same slots, single registration call. Additive prompt and corpus helpers (registerMemoryPromptSupplement, registerMemoryCorpusSupplement) are not affected.
Old: api.registerMemoryEmbeddingProvider(...) plus contracts.memoryEmbeddingProviders.New: api.registerEmbeddingProvider(...) plus contracts.embeddingProviders.The generic embedding provider contract is reusable outside memory and is the supported path for new providers. The memory-specific registration API remains wired as deprecated compatibility while existing providers migrate. Plugin inspection reports non-bundled usage as compatibility debt.
Old: return { ok, messageId, error } through ChannelSendRawResult and normalize it with createRawChannelSendResultAdapter(...).New: return OutboundDeliveryResult fields and attach the channel with createAttachedChannelResultAdapter(...). Failed sends should throw instead of returning an error string. The raw result type remains available until the next plugin-SDK major release.
Two legacy type aliases still exported from src/plugins/runtime/types.ts:The runtime method readSession is deprecated in favor of getSessionMessages. Same signature; the old method calls through to the new one.
The SQLite session/transcript flip removes or deprecates plugin-facing APIs that exposed active sessions.json stores, JSONL transcript paths, or lists of session files. Runtime plugins should use session identity and SDK runtime helpers instead of resolving or mutating active files.Legacy JSONL transcript files remain valid as import, archive, export, and support artifacts. They are no longer the steady-state runtime contract for active sessions.Official plugins released with v2026.7.1-beta.5 imported the four deprecated helpers above. openclaw/plugin-sdk/session-store-runtime keeps that exact bridge through 2026-10-12; new plugins must use the replacements. resolveStorePath(...) remains a supported SDK helper and is not part of this deprecation.openclaw plugins inspect --all --runtime reports non-bundled plugins whose load errors or diagnostics still reference these removed file APIs. The @openclaw/plugin-inspector advisory sweep must use version 0.3.17 or newer so external package scans also flag whole-store session helpers, session file-path helpers, legacy transcript file targets, and low-level transcript helpers before release.
Old: runtime.tasks.flow (singular) returned a live task-flow accessor.New: runtime.tasks.managedFlows keeps the managed TaskFlow mutation runtime for plugins that create, update, cancel, or run child tasks from a flow. Use runtime.tasks.flows when the plugin only needs DTO-based reads.
Removed after 2026-07-26.
Covered in How to migrate above. Included here for completeness: the removed embedded-runner-only api.registerEmbeddedExtensionFactory(...) path is replaced by api.registerAgentToolResultMiddleware(...) with an explicit runtime list in contracts.agentToolResultMiddleware.
OpenClawSchemaType re-exported from openclaw/plugin-sdk is now a one-line alias for OpenClawConfig. Prefer the canonical name.
Extension-level deprecations (inside bundled channel/provider plugins under extensions/) are tracked inside their own api.ts and runtime-api.ts barrels. They do not affect third-party plugin contracts and are not listed here. If you consume a bundled plugin’s local barrel directly, read the deprecation comments in that barrel before upgrading.

Talk and realtime voice migration

Realtime voice, telephony, meeting, and browser Talk code shares one Talk session controller exported by openclaw/plugin-sdk/realtime-voice. The controller owns the common Talk event envelope, active turn state, capture state, output-audio state, recent event history, and stale-turn rejection. Provider plugins own vendor-specific realtime sessions; surface plugins own capture, playback, telephony, and meeting quirks. All bundled surfaces run on the shared controller: browser relay, managed-room handoff, voice-call realtime, voice-call streaming STT, Google Meet realtime, and native push-to-talk. Gateway advertises one live Talk event channel in hello-ok.features.events: talk.event. New code should not call createTalkEventSequencer(...) directly unless implementing a low-level adapter or test fixture. Use the shared controller so turn-scoped events cannot be emitted without a turn id, stale turnEnd / turnCancel calls cannot clear a newer active turn, and output-audio lifecycle events stay consistent across telephony, meetings, browser relay, managed-room handoff, and native Talk clients. The public API shape:
Browser-owned WebRTC/provider-websocket sessions use talk.client.create, because the browser owns provider negotiation and media transport while the Gateway owns credentials, instructions, and tool policy. talk.session.* is the common Gateway-managed surface for gateway-relay realtime, gateway-relay transcription, and managed-room native STT/TTS sessions. Legacy configs that place realtime selectors beside talk.provider / talk.providers should be repaired with openclaw doctor --fix; runtime Talk does not reinterpret speech/TTS provider config as realtime provider config. The supported talk.session.create combinations are intentionally small: Method map for readers migrating from the older talk.realtime.* / talk.transcription.* / talk.handoff.* families (all removed): The unified control vocabulary is also deliberately narrow: Do not introduce provider or platform special cases in core to make this work. Core owns Talk session semantics. Provider plugins own vendor session setup. Voice-call and Google Meet own telephony/meeting adapters. Browser and native apps own device capture/playback UX.

Removal timeline

The deprecated public SDK subpaths below have registry-backed removal windows. They do not currently emit a runtime warning when an external plugin imports them; scripts/check-deprecated-api-usage.mjs only diagnoses imports from core and bundled plugin source. All core plugins have already migrated. External plugins should migrate before the next major release. Run pnpm plugins:boundary-report to see which compat records are due soonest for the surfaces your plugin uses.

Suppressing the warnings temporarily

This is a temporary escape hatch, not a permanent solution.