Skip to content

How to Migrate OpenClaw Plugins to the New Plugin SDK (2026)

I upgraded to OpenClaw 2026.3.22-beta.1 and my plugin stopped working immediately. The error was clear but frustrating:

Terminal Output
Error: Cannot find module 'openclaw/extension-api'
at PluginLoader.loadPlugin (src/plugin-loader.ts:142)
at async PluginManager.initialize (src/plugin-manager.ts:89)
at async OpenClaw.startup (src/index.ts:45)

This wasn’t a deprecation warning. The entire openclaw/extension-api module was gone. No compatibility shim, no fallback, nothing. Let me walk through how I fixed this.

The Problem: A Breaking Change With No Grace Period

The OpenClaw team described this as “the most painful breaking change for plugin authors.” They weren’t kidding.

Here’s what my plugin code looked like before:

my-plugin/src/index.ts
import { commands, window, workspace } from 'openclaw/extension-api'
export function activate() {
commands.registerCommand('myPlugin.hello', () => {
window.showInformationMessage('Hello from my plugin!')
})
const config = workspace.getConfiguration('myPlugin')
console.log('Config loaded:', config)
}

This code had worked for months. After the upgrade, it didn’t just warn me or suggest migration - it threw a hard error and refused to load.

Step 1: Read the Migration Guide (Don’t Skip This)

Before touching any code, I went to the official migration guide at docs.openclaw.ai/plugins/sdk-migration. This is critical because the SDK changes go beyond just import paths.

Key things I learned from the guide:

  • The new SDK uses modular imports from openclaw/plugin-sdk/*
  • Bundled plugins require an injected runtime for host-side operations
  • Some API signatures changed, not just the import paths

Step 2: Find All Deprecated Imports

I searched my plugin codebase for all references to the old API:

Terminal
grep -r "openclaw/extension-api" my-plugin/src/

Output:

Search Results
my-plugin/src/index.ts:1:import { commands, window, workspace } from 'openclaw/extension-api'
my-plugin/src/providers.ts:2:import { Range, Position, TextDocument } from 'openclaw/extension-api'
my-plugin/src/utils.ts:3:import { ConfigurationTarget, WorkspaceConfiguration } from 'openclaw/extension-api'

Three files. Not too bad, but I needed to update each one.

Step 3: Update Imports to the New SDK

The new plugin SDK uses a modular structure. Instead of importing everything from one module, I imported from specific submodules.

Before (Broken):

my-plugin/src/index.ts
import { commands, window, workspace } from 'openclaw/extension-api'
export function activate() {
commands.registerCommand('myPlugin.hello', () => {
window.showInformationMessage('Hello from my plugin!')
})
const config = workspace.getConfiguration('myPlugin')
console.log('Config loaded:', config)
}

After (Fixed):

my-plugin/src/index.ts
import { commands } from 'openclaw/plugin-sdk/commands'
import { window } from 'openclaw/plugin-sdk/window'
import { workspace } from 'openclaw/plugin-sdk/workspace'
export function activate() {
commands.registerCommand('myPlugin.hello', () => {
window.showInformationMessage('Hello from my plugin!')
})
const config = workspace.getConfiguration('myPlugin')
console.log('Config loaded:', config)
}

Step 4: Handle Type Imports

My providers file had type imports that also needed updating:

my-plugin/src/providers.ts (Before)
import { Range, Position, TextDocument } from 'openclaw/extension-api'
export class MyDocumentSymbolProvider {
provideDocumentSymbols(document: TextDocument): Range[] {
const position = new Position(0, 0)
const range = new Range(position, position)
return [range]
}
}
my-plugin/src/providers.ts (After)
import { Range, Position, TextDocument } from 'openclaw/plugin-sdk/types'
export class MyDocumentSymbolProvider {
provideDocumentSymbols(document: TextDocument): Range[] {
const position = new Position(0, 0)
const range = new Range(position, position)
return [range]
}
}

Step 5: Bundled Plugins Need Runtime Injection

My plugin wasn’t bundled, but if yours is, you need to handle it differently. Bundled plugins must use an injected runtime for host-side operations.

Here’s the pattern for bundled plugins:

bundled-plugin/src/index.ts
import type { PluginRuntime } from 'openclaw/plugin-sdk/runtime'
export function initialize(runtime: PluginRuntime) {
// Use runtime for host-side operations instead of direct imports
runtime.commands.registerCommand('bundledPlugin.hello', () => {
runtime.window.showInformationMessage('Hello from bundled plugin!')
})
const config = runtime.workspace.getConfiguration('bundledPlugin')
console.log('Config loaded:', config)
}

The runtime is injected by OpenClaw when loading bundled plugins. This is a security and isolation improvement in the new architecture.

Step 6: Run Type Checks

After updating all imports, I ran the TypeScript compiler:

Terminal
cd my-plugin && npm run type-check

Output:

TypeScript Output
src/utils.ts:15:23 - error TS2339: Property 'update' does not exist on type 'WorkspaceConfiguration'.
Property 'update' was moved to 'Configuration' interface.
15 config.update('setting', newValue)
~~~~~~
Found 1 error.

Ah, an API signature change. The update method moved from WorkspaceConfiguration to a separate Configuration interface.

Fix:

my-plugin/src/utils.ts
import { workspace } from 'openclaw/plugin-sdk/workspace'
import { Configuration } from 'openclaw/plugin-sdk/configuration'
export async function updateSetting(key: string, value: unknown) {
const config = new Configuration(workspace.getConfiguration('myPlugin'))
await config.update(key, value)
}

This is why reading the migration guide first matters - there were API surface changes beyond just import paths.

Step 7: Test Everything

I tested each plugin feature:

Terminal
npm run test
Test Output
PASS src/index.test.ts
activate
+ registers hello command (5ms)
+ loads configuration (2ms)
PASS src/providers.test.ts
MyDocumentSymbolProvider
+ provides document symbols (3ms)
Test Suites: 2 passed, 2 total
Tests: 3 passed, 3 total

All tests passed. But I also manually tested in OpenClaw to make sure the runtime behavior matched.

Step 8: Update Version and Publish

I updated my package.json:

my-plugin/package.json
{
"name": "my-openclaw-plugin",
"version": "2.0.0",
"engines": {
"openclaw": ">=2026.3.22"
}
}

Then published to ClawHub:

Terminal
openclaw publish --hub clawhub
Publish Output
Publishing [email protected] to ClawHub...
+ Successfully published!
View at: https://clawhub.io/plugins/my-openclaw-plugin

Migration Checklist

Here’s the complete checklist I followed:

  • Read docs.openclaw.ai/plugins/sdk-migration in full
  • Identify all files using openclaw/extension-api imports
  • Update imports to openclaw/plugin-sdk/*
  • Update bundled plugins to use injected runtime (if applicable)
  • Run type checks and fix any API changes
  • Test all plugin functionality
  • Update plugin version number
  • Update plugin documentation
  • Publish to ClawHub
  • Notify users of the update

What I Wish I Had Known

  1. Don’t rush. The migration guide exists for a reason. Read it completely before touching code.

  2. Check for API signature changes. It’s not just import paths that changed. Some method signatures moved or changed behavior.

  3. Test in staging first. Don’t publish until you’ve manually verified everything works.

  4. Check your dependencies. If your plugin depends on other community plugins, verify they’ve also migrated.

  5. Publish to ClawHub. The OpenClaw team recommends ClawHub for community plugins. It increases visibility and helps users discover compatible plugins.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments