Skip to main content

Plugin Runtime

Native OpenClaw plugins receive a trusted runtime through api.runtime. Use it for host-owned operations that should stay inside OpenClaw’s runtime:
  • reading and writing config
  • agent/session helpers
  • system commands with OpenClaw timeouts
  • media, speech, image-generation, and web-search runtime calls
  • channel-owned helpers for bundled channel plugins

When to use runtime vs focused SDK helpers

  • Use focused SDK helpers when a public subpath already models the job.
  • Use api.runtime.* when the host owns the operation or state.
  • Prefer hooks for loose integrations that do not need tight in-process access.

Runtime namespaces

NamespaceWhat it covers
api.runtime.configLoad and persist OpenClaw config
api.runtime.agentAgent workspace, identity, timeouts, session store
api.runtime.systemSystem events, heartbeats, command execution
api.runtime.mediaFile/media loading and transforms
api.runtime.ttsSpeech synthesis and voice listing
api.runtime.mediaUnderstandingImage/audio/video understanding
api.runtime.imageGenerationImage generation providers
api.runtime.webSearchRuntime web-search execution
api.runtime.modelAuthResolve model/provider credentials
api.runtime.subagentSpawn, wait, inspect, and delete subagent sessions
api.runtime.channelChannel-heavy helpers for native channel plugins

Example: read and persist config

import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";

export default definePluginEntry({
  id: "talk-settings",
  name: "Talk Settings",
  description: "Example runtime config write",
  register(api: OpenClawPluginApi) {
    api.registerCommand({
      name: "talk-mode",
      description: "Enable talk mode",
      handler: async () => {
        const cfg = api.runtime.config.loadConfig();
        const nextConfig = {
          ...cfg,
          talk: {
            ...cfg.talk,
            enabled: true,
          },
        };
        await api.runtime.config.writeConfigFile(nextConfig);
        return { text: "talk mode enabled" };
      },
    });
  },
});

Example: use a runtime service owned by OpenClaw

const cfg = api.runtime.config.loadConfig();
const voices = await api.runtime.tts.listVoices({
  provider: "openai",
  cfg,
});

return {
  text: voices.map((voice) => `${voice.name ?? voice.id}: ${voice.id}`).join("\n"),
};

createPluginRuntimeStore(...)

Plugin modules often need a small mutable slot for runtime-backed helpers. Use plugin-sdk/runtime-store instead of an unguarded let runtime.
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
import { channelPlugin } from "./src/channel.js";

const runtimeStore = createPluginRuntimeStore<{
  logger: { info(message: string): void };
}>("Example Channel runtime not initialized");

export function setExampleRuntime(runtime: { logger: { info(message: string): void } }) {
  runtimeStore.setRuntime(runtime);
}

export function getExampleRuntime() {
  return runtimeStore.getRuntime();
}

export default defineChannelPluginEntry({
  id: "example-channel",
  name: "Example Channel",
  description: "Example runtime store usage",
  plugin: channelPlugin,
  setRuntime: setExampleRuntime,
});
createPluginRuntimeStore(...) gives you:
  • setRuntime(next)
  • clearRuntime()
  • tryGetRuntime()
  • getRuntime()
getRuntime() throws with your custom message if the runtime was never set.

Channel runtime note

api.runtime.channel.* is the heaviest namespace. It exists for native channel plugins that need tight coupling with the OpenClaw messaging stack. Prefer narrower subpaths such as:
  • plugin-sdk/channel-pairing
  • plugin-sdk/channel-actions
  • plugin-sdk/channel-feedback
  • plugin-sdk/channel-lifecycle
Use api.runtime.channel.* when the operation is clearly host-owned and there is no smaller public seam.

Runtime safety guidelines

  • Do not cache config snapshots longer than needed.
  • Prefer createPluginRuntimeStore(...) for shared module state.
  • Keep runtime-backed code behind small local helpers.
  • Avoid reaching into runtime namespaces you do not need.