Skip to content

Configure OpenClaw Plugins: Create Your Manifest File

This guide covers the native OpenClaw plugin manifest. If you are using compatible bundle layouts, check Plugin bundles instead.

Compatible bundle formats use different manifest files:

  • Codex bundles (.codex-plugin/plugin.json)
  • Claude bundles (.claude-plugin/plugin.json or default layouts)
  • Cursor bundles (.cursor-plugin/plugin.json)
  • Other auto-detected layouts

OpenClaw detects these layouts automatically, but it does not validate them against the openclaw.plugin.json schema described here.

For compatible bundles, OpenClaw reads bundle metadata, skill roots, Claude command roots, and settings.json defaults. It also looks for Claude bundle LSP defaults and supported hook packs when the layout matches runtime expectations.

Every native OpenClaw plugin must include an openclaw.plugin.json file in the plugin root. OpenClaw uses this manifest to validate configuration without executing any plugin code. Missing or invalid manifests are treated as errors and will block configuration validation. This ensures your setup is correct before the runtime starts.

You can find the full plugin system guide at Plugins. For the native capability model and external-compatibility guidance, see the Capability model.

The openclaw.plugin.json file contains the metadata OpenClaw reads before it loads your plugin code.

Use it for:

  • plugin identity
  • config validation
  • auth and onboarding metadata that should be available without booting plugin runtime
  • alias and auto-enable metadata that should resolve before plugin runtime loads
  • shorthand model-family ownership metadata that should auto-activate the plugin before runtime loads
  • static capability ownership snapshots used for bundled compat wiring and contract coverage
  • channel-specific config metadata that should merge into catalog and validation surfaces without loading runtime
  • config UI hints

Do not use it for:

  • registering runtime behavior and declaring code entrypoints
  • npm install metadata

These details belong in your actual plugin code and your package.json file.

{
"id": "voice-call",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {}
}
}
{
"id": "openrouter",
"name": "OpenRouter",
"description": "OpenRouter provider plugin",
"version": "1.0.0",
"providers": ["openrouter"],
"modelSupport": {
"modelPrefixes": ["router-"]
},
"cliBackends": ["openrouter-cli"],
"providerAuthEnvVars": {
"openrouter": ["OPENROUTER_API_KEY"]
},
"providerAuthAliases": {
"openrouter-coding": "openrouter"
},
"channelEnvVars": {
"openrouter-chatops": ["OPENROUTER_CHATOPS_TOKEN"]
},
"providerAuthChoices": [
{
"provider": "openrouter",
"method": "api-key",
"choiceId": "openrouter-api-key",
"choiceLabel": "OpenRouter API key",
"groupId": "openrouter",
"groupLabel": "OpenRouter",
"optionKey": "openrouterApiKey",
"cliFlag": "--openrouter-api-key",
"cliOption": "--openrouter-api-key <key>",
"cliDescription": "OpenRouter API key",
"onboardingScopes": ["text-inference"]
}
],
"uiHints": {
"apiKey": {
"label": "API key",
"placeholder": "sk-or-v1-...",
"sensitive": true
}
},
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"apiKey": {
"type": "string"
}
}
}
}
FieldRequiredTypeWhat it means
idYesstringCanonical plugin id. This is the id used in plugins.entries.<id>.
configSchemaYesobjectInline JSON Schema for this plugin’s config.
enabledByDefaultNotrueMarks a bundled plugin as enabled by default. Omit it, or set any non-true value, to leave the plugin disabled by default.
legacyPluginIdsNostring[]Legacy ids that normalize to this canonical plugin id.
autoEnableWhenConfiguredProvidersNostring[]Provider ids that should auto-enable this plugin when auth, config, model refs, or other settings mention them.
kindNo"memory" | "context-engine"Declares an exclusive plugin kind used by plugins.slots.*.
channelsNostring[]Channel ids owned by this plugin. Used for discovery and config validation.
providersNostring[]Provider ids owned by this plugin.
modelSupportNoobjectManifest-owned shorthand model-family metadata used to auto-load the plugin before runtime.
cliBackendsNostring[]CLI inference backend ids owned by this plugin. Used for startup auto-activation from explicit config refs.
commandAliasesNoobject[]Command names owned by this plugin that should produce plugin-aware config and CLI diagnostics before runtime loads.
providerAuthEnvVarsNoRecord<string, string[]>Cheap provider-auth env metadata that OpenClaw can inspect without loading plugin code.
providerAuthAliasesNoRecord<string, string>Provider ids that should reuse another provider id for auth lookup, for example a coding provider that shares the base provider API key and auth profiles.
channelEnvVarsNoRecord<string, string[]>Cheap channel env metadata that OpenClaw can inspect without loading plugin code. Use this for env-driven channel setup or auth surfaces that generic startup/config helpers should see.
providerAuthChoicesNoobject[]Cheap auth-choice metadata for onboarding pickers, preferred-provider resolution, simple CLI flag wiring, and setup helpers.
contractsNoobjectStatic bundled capability snapshot for speech, realtime transcription, realtime voice, media-understanding, image-generation, music-generation, video-generation, web-fetch, web search, and tool ownership.
channelConfigsNoRecord<string, object>Manifest-owned channel config metadata merged into discovery and validation surfaces before runtime loads.
skillsNostring[]Skill directories to load, relative to the plugin root.
nameNostringHuman-readable plugin name.
descriptionNostringShort summary shown in plugin surfaces.
versionNostringInformational plugin version.
uiHintsNoRecord<string, object>UI labels, placeholders, sensitivity hints, and display options for config fields.

You use providerAuthChoices to define how users handle onboarding or authentication. OpenClaw checks these entries before the provider runtime even starts.

FieldRequiredTypeWhat it means
providerYesstringProvider id this choice belongs to.
methodYesstringAuth method id to dispatch to.
choiceIdYesstringStable auth-choice id used by onboarding and CLI flows.
choiceLabelNostringUser-facing label. If omitted, OpenClaw falls back to choiceId.
choiceHintNostringShort helper text for the picker.
assistantPriorityNonumberLower values sort earlier in assistant-driven interactive pickers.
assistantVisibilityNo"visible" | "manual-only"Hide the choice from assistant pickers while still allowing manual CLI selection.
deprecatedChoiceIdsNostring[]Legacy choice ids that should redirect users to this replacement choice.
groupIdNostringOptional group id for grouping related choices.
groupLabelNostringUser-facing label for that group.
groupHintNostringShort helper text for the group.
optionKeyNostringInternal option key for simple one-flag auth flows.
cliFlagNostringCLI flag name, such as --openrouter-api-key.
cliOptionNostringFull CLI option shape, such as --openrouter-api-key <key>.
cliDescriptionNostringDescription used in CLI help.
onboardingScopesNoArray&lt;"text-inference" | "image-generation"&gt;Which onboarding surfaces this choice should appear in. If omitted, it defaults to ["text-inference"].

You should use commandAliases if your plugin owns a runtime command that users might accidentally put in plugins.allow or try to run as a root CLI command. This helps OpenClaw provide diagnostics without needing to import your plugin’s runtime code.

{
"commandAliases": [
{
"name": "dreaming",
"kind": "runtime-slash",
"cliCommand": "memory"
}
]
}
FieldRequiredTypeWhat it means
nameYesstringCommand name that belongs to this plugin.
kindNo"runtime-slash"Marks the alias as a chat slash command rather than a root CLI command.
cliCommandNostringRelated root CLI command to suggest for CLI operations, if one exists.

The uiHints map lets you attach small rendering hints to your config fields. It helps you control how settings appear to the user.

{
"uiHints": {
"apiKey": {
"label": "API key",
"help": "Used for OpenRouter requests",
"placeholder": "sk-or-v1-...",
"sensitive": true
}
}
}

Each field hint can include:

FieldTypeWhat it means
labelstringUser-facing field label.
helpstringShort helper text.
tagsstring[]Optional UI tags.
advancedbooleanMarks the field as advanced.
sensitivebooleanMarks the field as secret or sensitive.
placeholderstringPlaceholder text for form inputs.

You should use contracts strictly for static metadata about which capabilities your plugin owns. This allows OpenClaw to check these capabilities without importing the plugin runtime.

{
"contracts": {
"speechProviders": ["openai"],
"realtimeTranscriptionProviders": ["openai"],
"realtimeVoiceProviders": ["openai"],
"mediaUnderstandingProviders": ["openai", "openai-codex"],
"imageGenerationProviders": ["openai"],
"videoGenerationProviders": ["qwen"],
"webFetchProviders": ["firecrawl"],
"webSearchProviders": ["gemini"],
"tools": ["firecrawl_search", "firecrawl_scrape"]
}
}

Each list is optional:

FieldTypeWhat it means
speechProvidersstring[]Speech provider ids this plugin owns.
realtimeTranscriptionProvidersstring[]Realtime-transcription provider ids this plugin owns.
realtimeVoiceProvidersstring[]Realtime-voice provider ids this plugin owns.
mediaUnderstandingProvidersstring[]Media-understanding provider ids this plugin owns.
imageGenerationProvidersstring[]Image-generation provider ids this plugin owns.
videoGenerationProvidersstring[]Video-generation provider ids this plugin owns.
webFetchProvidersstring[]Web-fetch provider ids this plugin owns.
webSearchProvidersstring[]Web-search provider ids this plugin owns.
toolsstring[]Agent tool names this plugin owns for bundled contract checks.

You should use channelConfigs when a channel plugin needs access to config metadata before the runtime actually loads. It is a great way to keep things fast by providing necessary details early.

{
"channelConfigs": {
"matrix": {
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"homeserverUrl": { "type": "string" }
}
},
"uiHints": {
"homeserverUrl": {
"label": "Homeserver URL",
"placeholder": "https://matrix.example.com"
}
},
"label": "Matrix",
"description": "Matrix homeserver connection",
"preferOver": ["matrix-legacy"]
}
}
}

Each channel entry can include these fields:

FieldTypeWhat it means
schemaobjectJSON Schema for channels.<id>. You must include this for each declared channel config entry.
uiHintsRecord<string, object>Optional UI labels, placeholders, or hints for sensitive data in that config section.
labelstringA label for the channel used in pickers and inspect views when runtime metadata isn’t ready.
descriptionstringA short description for the channel used in inspect and catalog views.
preferOverstring[]IDs of legacy or lower-priority plugins that this channel should rank higher than in the UI.

You can use modelSupport to help OpenClaw identify your provider plugin from shorthand model IDs like gpt-5.4 or claude-sonnet-4.6. This identification happens before the plugin runtime loads.

{
"modelSupport": {
"modelPrefixes": ["gpt-", "o1", "o3", "o4"],
"modelPatterns": ["^computer-use-preview"]
}
}

OpenClaw follows this specific order of precedence:

  • Explicit provider/model references use the metadata from the owning providers manifest.
  • modelPatterns always beat modelPrefixes.
  • If both a non-bundled plugin and a bundled plugin match, the non-bundled plugin wins.
  • Any remaining ambiguity is ignored until you or the config specify a provider.

Here are the fields you can use:

FieldTypeWhat it means
modelPrefixesstring[]Prefixes that match shorthand model IDs using startsWith.
modelPatternsstring[]Regex sources matched against shorthand model IDs after any profile suffix is removed.

Keep in mind that legacy top-level capability keys are deprecated. You should use openclaw doctor --fix to move speechProviders, realtimeTranscriptionProviders, realtimeVoiceProviders, mediaUnderstandingProviders, imageGenerationProviders, videoGenerationProviders, webFetchProviders, and webSearchProviders under contracts. Normal manifest loading won’t treat those top-level fields as capability ownership anymore.

When you’re building a plugin, you’ll work with two main files. They have different roles, and it’s important to know which one handles what.

FileUse it for
openclaw.plugin.jsonDiscovery, config validation, auth-choice metadata, and UI hints that must exist before plugin code runs
package.jsonnpm metadata, dependency installation, and the openclaw block used for entrypoints, install gating, setup, or catalog metadata

If you aren’t sure where a piece of metadata belongs, you can follow this rule:

  • If OpenClaw needs to know it before loading your plugin code, put it in openclaw.plugin.json.
  • If it is about packaging, entry files, or npm install behavior, put it in package.json.

Some pre-runtime plugin metadata lives in your package.json under the openclaw block instead of the manifest.

Here are the important examples:

FieldWhat it means
openclaw.extensionsDeclares native plugin entrypoints.
openclaw.setupEntryLightweight setup-only entrypoint used during onboarding and deferred channel startup.
openclaw.channelCheap channel catalog metadata like labels, docs paths, aliases, and selection copy.
openclaw.channel.configuredStateLightweight configured-state checker metadata that can answer “does env-only setup already exist?” without loading the full channel runtime.
openclaw.channel.persistedAuthStateLightweight persisted-auth checker metadata that can answer “is anything already signed in?” without loading the full channel runtime.
openclaw.install.npmSpec / openclaw.install.localPathInstall/update hints for bundled and externally published plugins.
openclaw.install.defaultChoicePreferred install path when multiple install sources are available.
openclaw.install.minHostVersionMinimum supported OpenClaw host version, using a semver floor like >=2026.3.22.
openclaw.install.allowInvalidConfigRecoveryAllows a narrow bundled-plugin reinstall recovery path when config is invalid.
openclaw.startup.deferConfiguredChannelFullLoadUntilAfterListenLets setup-only channel surfaces load before the full channel plugin during startup.

OpenClaw enforces openclaw.install.minHostVersion during install and manifest registry loading. If you use an invalid value, it gets rejected. If the value is valid but newer than the host, the host skips the plugin.

The openclaw.install.allowInvalidConfigRecovery field is quite specific. It doesn’t make every broken config installable. Right now, it only lets install flows recover from specific stale bundled-plugin upgrade failures, like a missing bundled plugin path or a stale channels.<id> entry for that same plugin. Other config errors will still block the install and point you to openclaw doctor --fix.

For openclaw.channel.persistedAuthState, you are providing package metadata for a tiny checker module:

{
"openclaw": {
"channel": {
"id": "whatsapp",
"persistedAuthState": {
"specifier": "./auth-presence",
"exportName": "hasAnyWhatsAppAuth"
}
}
}
}

You should use this when setup, doctor, or configured-state flows need a quick yes/no auth probe before the full channel plugin loads. The target export should be a small function that only reads persisted state. You should not route it through the full channel runtime barrel.

The openclaw.channel.configuredState field follows the same pattern for quick environment-only checks:

{
"openclaw": {
"channel": {
"id": "telegram",
"configuredState": {
"specifier": "./configured-state",
"exportName": "hasTelegramConfiguredState"
}
}
}
}

Use this when a channel can determine its configured state from environment variables or other tiny inputs. If your check needs full config resolution or the real channel runtime, you should keep that logic in the plugin config.hasConfiguredState hook instead.

  • Every plugin must ship a JSON Schema, even if it accepts no config.
  • An empty schema is fine (for example, { "type": "object", "additionalProperties": false }).
  • Schemas are validated at config read/write time, not at runtime.

When you’re setting things up, the system keeps a close eye on your configuration to make sure everything runs smoothly. If you use unknown keys under channels.*, you’ll run into errors unless a plugin manifest specifically declares that channel ID. You also need to make sure that any IDs you use in plugins.entries.<id>, plugins.allow, plugins.deny, or plugins.slots.* are actually discoverable. If the system can’t find them, it’ll throw an error.

If a plugin is installed but its manifest or schema is missing or broken, validation will fail, and the Doctor tool will let you know exactly what went wrong. On the other hand, if you have configuration for a plugin that’s currently disabled, the system keeps the config but gives you a warning in the logs and Doctor.

Check out the Configuration reference for the full plugins.* schema.

You’ll need a manifest for any native OpenClaw plugins, even if you’re loading them from your local filesystem. Just remember that the manifest is for discovery and validation; the runtime still loads the actual plugin module separately.

The good news is that native manifests use JSON5. This means you can use comments, trailing commas, and unquoted keys, provided the final result is an object. Just stick to the documented fields, because the loader ignores custom top-level keys.

There are a few “cheap metadata paths” designed to keep things fast without booting the full plugin runtime just to inspect environment names:

  • providerAuthEnvVars handles auth probes and env-marker validation.
  • providerAuthAliases lets different provider variants share auth settings or API-key onboarding without hardcoding links in the core system.
  • channelEnvVars manages shell-env fallbacks and setup prompts.
  • providerAuthChoices takes care of auth-choice pickers and CLI flag registration before the provider runtime loads. For more complex wizard metadata that needs actual provider code, you’ll want to look at Provider runtime hooks.

If you’re working with exclusive plugin kinds, you’ll select them through plugins.slots.*. For example, kind: "memory" is selected by plugins.slots.memory, and kind: "context-engine" is selected by plugins.slots.contextEngine (which defaults to the built-in legacy option).

You don’t have to include channels, providers, cliBackends, or skills if your plugin doesn’t use them. Also, if your plugin needs native modules, make sure you document the build steps and any package manager requirements, like pnpm’s allow-build-scripts (pnpm rebuild <package>).

OpenClaw

OpenClaw Expert

Still stuck?

If this page didn't answer your case, ask OpenClaw Expert for step-by-step guidance.