fetchAcme* in the samples are placeholders for your own vendor
API calls, not exported OpenClaw functions.
New to OpenClaw plugins? Read Getting Started
first for package structure and manifest setup.
Import an existing credential during sign-in
An auth method can declarecredentialImport with a migrationProviderId,
an exact itemId, and a credentialKind (api_key, oauth, or token).
models auth login asks that migration owner for an auth-only plan before
starting interactive sign-in. --force, --profile-id, and --set-default skip
import. --set-default uses the auth method’s recommended model through the normal
sign-in flow.
The migration plugin declares its ID in contracts.migrationProviders and can
export buildMigrationProvider() from a top-level migration-provider-api.ts
public artifact. Keep that entry lightweight. Bundled plugins and enabled
installed plugins can supply it without replacing the running plugin registry.
Explicitly disabled or denied migration owners cannot execute their artifacts.
The existing bundled migration compatibility rules still apply.
The login caller selects only the declared auth item. Its details must contain
the matching provider and credentialKind. A migrated result also supplies
the saved profileId. The owner must honor cancellation, reread the selected
source before persistence, and reject a changed credential. Login passes
configPatchMode: "none" so import preserves model defaults and restrictions.
Unavailable storage or an unusable matching OAuth profile continues to interactive
sign-in. A matching account identity alone does not make expired credentials usable.
A failed selected import stops the operation instead of silently starting a different login.
Handle model access after sign-in
Existing consumers ofrunModelsAuthLoginFlow from
openclaw/plugin-sdk/provider-auth-login-flow-runtime must handle a selection
after credentials are saved. When effective restrictions can hide the provider’s
models, the existing prompter.select receives these options:
Render the supplied message and options, and return the selected option’s value.
Do not assume that every
select call chooses a provider or auth method. Neither
choice activates a new default model. No choice is requested when restrictions
are absent or already allow the whole provider.
Canceling or rejecting this post-save selection does not undo saved credentials.
The flow throws ProviderAuthConfigApplyError, which extends
ProviderCredentialsSavedError; report that credentials were saved instead of
treating it as a failed credential exchange. Cancellation at the selection leaves
restrictions unchanged. A later application failure can leave the policy saved
but not active in the running Gateway. Keep credential persistence and model
visibility outcomes distinct.
Defer the choice to a later reply
For chat buttons, pass the synchronousonModelAccessRequested callback. It
receives a PreparedProviderModelAccess request and replaces the post-save
select call; it does not apply the choice. Retain the prepared request until
login finishes. Use createProviderLoginFlowRegistry and
reserveProviderLoginFlow to reserve only the credential exchange.
After login finishes, call offerProviderLoginModelAccess with flows, flowKey,
prepared, and terminalMessage. Deliver its structured reply. Always release
the login in finally with releaseProviderLoginFlow({ flows, flowKey, record }).
The pending model-access question has its own lifetime and remains answerable
after that release; it does not block another login.
Pass the later command to answerProviderLoginModelAccess with flows, flowKey,
agentId, command, runtime, readConfig, and assertCurrent; signal is
optional. readConfig must return the host’s current config. The owner validates
the answer, applies the choice, and consumes that question. Expired or conflicting
choices receive a fresh question based on current restrictions.
Use cancelProviderLoginFlow({ flows, flowKey }) to cancel either pending phase.
Do not reconstruct a wildcard write from button text or reuse a prepared request
for a new login.
Keep hosted writes authorized
Hosted callers supplysignal and assertCurrent to check the current login,
sender authority, and selected provider/method before effects and after awaited
work. An abort signal or matching login identifier alone is not current
authorization. beforePersistentEffect remains the credential-persistence
preparation callback. Browser authorization ends after the credential phase;
the later model choice uses the current conversation or wizard authority.
For a deferred choice, pass the answering command’s current authority check as
answerProviderLoginModelAccess.assertCurrent. Use its config argument when
supplied: it is the policy writer’s current config. Otherwise read the host’s
current config. The original login callback does not authorize a later command.
Let the shared owner report the visibility outcome:
a saved policy is not proof that the running Gateway applied it.
Walkthrough
1
Package and manifest
Step 1: Package and manifest
setup.providers[].envVars lets OpenClaw detect credentials without
loading your plugin runtime. Add providerAuthAliases when a provider
variant should reuse another provider id’s auth. modelSupport is
optional and lets OpenClaw auto-load your provider plugin from shorthand
model ids like acme-large before runtime hooks exist. openclaw.compat
and openclaw.build in package.json are required for ClawHub
publishing (openclaw.compat.pluginApi and openclaw.build.openclawVersion
are the two required fields. minGatewayVersion falls back to
openclaw.install.minHostVersion when omitted).The version strings in the sample manifests are placeholders. Pin them to
the OpenClaw release your plugin builds and tests against.2
Register the provider
A minimal text provider needs an OpenClaw keeps the inline value only while staged validation runs. At the
final persistence boundary it writes the value to the protected local store
and saves a
id, label, auth, and catalog.
catalog is the provider-owned runtime/config hook. It can call live
vendor APIs and returns models.providers entries.index.ts
registerModelCatalogProvider is the newer control-plane catalog surface
for list/help/picker UI, covering text, voice, image_generation,
video_generation, and music_generation rows. Keep vendor endpoint
calls and response mapping in the plugin. OpenClaw owns the shared row
shape, source labels, and help rendering.That is a working provider. Users can now run
openclaw onboard --acme-ai-api-key <key> and select
acme-ai/acme-large as their model.For provider-key lookup and selection from an already loaded auth store,
import findNormalizedProviderValue and resolveAuthProfileOrder from
openclaw/plugin-sdk/provider-auth. This keeps provider entrypoints from
loading the full agent runtime just to select a credential. The deprecated
agent-runtime exports remain available for compatibility. Use the narrower
provider-auth route in new code. See the removal
timeline for the dates and gates
that govern deprecated surfaces named on this page and its child pages.Bundled custom API-key methods can use captureProviderApiKey and
persistProviderApiKey from openclaw/plugin-sdk/provider-auth-api-key
when vendor prompts or validation need to stay between auth steps.
captureProviderApiKey(ctx, options) accepts the existing token/provider,
environment, and prompt options. It returns the resolved apiKey for
validation alongside the original storage input and mode, without
saving credentials. Build returned profiles from input and mode so
SecretRefs remain references. The helper preserves the context’s staged
workspace and secret-storage prompt preference.persistProviderApiKey(ctx, profileId, { provider, resolved, metadata })
accepts an already resolved non-interactive key. It leaves profile-sourced
credentials unchanged, returns false if credential conversion fails,
and propagates persistence errors. Keep vendor checks before this call;
apply auth-profile config and model defaults afterward through their
existing owners. Neither helper chooses an endpoint or model.A custom interactive auth method that mints a static token or API key can
request protected persistence on its returned profile:tokenRef or keyRef in the auth profile. namePrefix must be
an uppercase environment-style name. OpenClaw adds a stable suffix derived
from the provider and final profile id so multiple profiles remain separate.
Use this only for provider-minted static credentials, not rotating OAuth
credentials or values already supplied as SecretRefs.For live /models discovery, catalog helpers, pricing normalization, and
the narrower single-provider entry point, see Provider model
catalogs.3
Add dynamic model resolution
If your provider accepts arbitrary model IDs (like a proxy or router),
add If resolving requires a network call, return the requested model directly
from
resolveDynamicModel:prepareDynamicModel. OpenClaw applies the same configured overrides
and normalization as synchronous dynamic resolution. Existing hooks that
return nothing still retry resolveDynamicModel after preparation.4
Add runtime hooks (as needed)
Most providers only need
catalog + resolveDynamicModel. Add hooks
incrementally as your provider requires them.Start with the shared family builders in Provider hook
families, then wire individual
hooks with Provider hook wiring.5
Add extra capabilities (optional)
Step 5: Add extra capabilities
A provider plugin can register embeddings, speech, realtime transcription, realtime voice, media understanding, image generation, video generation, web fetch, and web search alongside text inference. OpenClaw classifies this as a hybrid-capability plugin - the recommended pattern for company plugins (one plugin per vendor). See Internals: Capability Ownership.Register the audio capabilities from Provider voice capabilities. Register embeddings, generation, fetch, and search from Provider media and search.6
Test
Step 6: Test
src/provider.test.ts
Publish to ClawHub
Provider plugins publish the same way as any other external code plugin:clawhub skill publish <path> is a different command for publishing a skill
folder, not a plugin package - do not use it here.
File structure
Catalog order reference
catalog.order controls when your catalog merges relative to built-in
providers:
Next steps
- Channel Plugins - if your plugin also provides a channel
- SDK Runtime -
api.runtimehelpers (TTS, search, subagent) - SDK Overview - full subpath import reference
- Plugin Internals - hook details and bundled examples