#How can we create a custom web search tool or provider to add to openclaw
39 messages · Page 1 of 1 (latest)
Yep, best path is a plugin.
If you want:
- a brand new tool name, use
api.registerTool(...) - a new backend for built in
web_search, useapi.registerWebSearchProvider(...)
For a custom web_search provider, the shape is basically:
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { Type } from "@sinclair/typebox";
import { createWebSearchProviderContractFields } from "openclaw/plugin-sdk/provider-web-search-contract";
export default definePluginEntry({
id: "acme",
name: "Acme Search",
description: "Custom web search provider",
register(api) {
api.registerWebSearchProvider({
id: "acme-search",
label: "Acme Search",
hint: "Search via Acme API",
envVars: ["ACME_SEARCH_API_KEY"],
placeholder: "acme-...",
signupUrl: "https://example.com",
credentialPath: "plugins.entries.acme.config.webSearch.apiKey",
autoDetectOrder: 25,
...createWebSearchProviderContractFields({
credentialPath: "plugins.entries.acme.config.webSearch.apiKey",
searchCredential: { type: "top-level" },
[4/15/2026 12:30 AM] Nero: 📋 Complete Implementation & Issue Report
───
What We Built
Goal
Replace OpenClaw's web search backend with qwen-proxy's free web search API (1000 req/day) so the web_search tool calls our local qwen-proxy instead of the current provider.
Implementation
- qwen-proxy service (✅ Working)
• Installed via npm install -g qwen-proxy
• Running headless on http://localhost:6969
• 1 Qwen OAuth account, healthy, auto-refreshing tokens
• POST /v1/web/search returns structured results with reranking
• Auto-start via crontab @reboot
• Status: Working perfectly. Tested and verified.
- OpenClaw plugin (❌ Not Working)
• Created at ~/.openclaw/extensions/qwen-search/
• 3 files: package.json, openclaw.plugin.json, index.js
• Uses api.registerWebSearchProvider() from the Plugin SDK
• Configured like DuckDuckGo (the simplest bundled provider)
• autoDetectOrder: 150, requiresCredential: false
• Plugin shows as loaded in openclaw plugins list
• tools.web.search.provider set to "qwen-search"
• Gateway restarted after every change
───
The Issue
Symptom
[WEB_SEARCH_PROVIDER_INVALID_AUTODETECT] tools.web.search.provider is "qwen-search".
Falling back to auto-detect precedence.
The web_search tool silently falls back to auto-detect (picks Perplexity/DuckDuckGo) and completely ignores our plugin.
Root Cause
OpenClaw's web search provider resolution has two paths:
- Bundled providers — The 12 built-in ones (Brave, SearXNG, DuckDuckGo, Ollama, Perplexity, etc.) These are hardcoded in the OpenClaw binary and always available.
- Plugin providers — Providers registered via api.registerWebSearchProvider(). These load at runtime.
The bug is in the config resolution layer. When you set tools.web.search.provider: "qwen-search", the resolution code does this:
configuredCredential: { pluginId: "acme" },
selectionPluginId: "acme",
}),
createTool: ({ searchConfig }) => ({
description: "Search the web with Acme",
parameters: Type.Object({
query: Type.String(),
count: Type.Optional(Type.Number({ minimum: 1, maximum: 10 })),
}),
async execute(args) {
const apiKey =
typeof searchConfig?.apiKey === "string"
? searchConfig.apiKey
: process.env.ACME_SEARCH_API_KEY;
// call your API here, then return plain JSON
return {
results: [
{ title: "Example", url: "https://example.com", snippet: "..." }
]
};
},
}),
});
},
});
Also add this to openclaw.plugin.json:
{
"id": "acme",
"name": "Acme Search",
"description": "Custom web search provider",
"contracts": {
"webSearchProviders": ["acme-search"]
}
}
Then install + restart:
openclaw plugins install ./my-plugin
openclaw gateway restart
And select it with config:
{
tools: {
web: {
search: {
provider: "acme-search"
}
}
}
}
Two important notes:
- Current source expects provider owned
createTool(...)directly. The oldercreatePluginBackedWebSearchProvider(...)path is deprecated.
- If you only want a separate tool like
my_search,registerTool(...)is simpler than plugging intoweb_search.
Best docs:
- https://docs.openclaw.ai/plugins/building-plugins
- https://docs.openclaw.ai/plugins/sdk-provider-plugins
- https://docs.openclaw.ai/tools/web
- https://docs.openclaw.ai/tools/plugin
If you want, I can sketch a complete minimal plugin repo for a specific search API next.
- Load all bundled providers
- Check if any custom plugin providers exist (getHasCustomWebSearchRisk)
- If no "risk" detected → use only bundled providers via resolveBundledWebSearchProvidersFromPublicArtifacts
- Filter the list with searchCompatibilityOnlyPluginIds
- Try to match "qwen-search" in the filtered list
- Not found → INVALID_AUTODETECT → fall back to auto-detect
The critical filter (searchCompatibilityOnlyPluginIds) only includes bundled plugin IDs. Custom plugins are excluded from the candidate list entirely. So even though registerWebSearchProvider() successfully registers our provider in the plugin registry, the config resolution layer never sees it when matching the configured provider name.
Why DuckDuckGo Works But We Don't
DuckDuckGo is bundled in openclaw/dist/extensions/duckduckgo/ — it ships with the binary. Its provider ID is in the hardcoded searchCompatibilityOnlyPluginIds list. Our plugin is a global extension loaded from ~/.openclaw/extensions/ — it's never in the candidate list.
The WW-AI-Lab Plugin
I found @ww-ai-lab/openclaw-web-search which does the exact same thing (registers qwen + metaso providers). It requires >=2026.3.28. We're on 2026.4.14. It might work on their version, or they might hit the same wall — but it confirms the SDK interface exists and is meant to work. This is a bug in the resolution layer, not in our code.
What Works vs What Doesn't
| Component | Status | Detail |
|---|---|---|
| [4/15/2026 12:30 AM] Nero: | qwen-proxy service | ✅ Working |
| Plugin loading | ✅ Working | Shows as "loaded" in plugin list |
| registerWebSearchProvider() | ✅ Working | No errors during registration |
| Provider matching | ❌ Broken | Config resolver filters out non-bundled providers |
| tools.web.search.provider: "qwen-search" | ❌ Rejected | Falls back to auto-detect |
| [4/15/2026 12:30 AM] Nero: | Config schema validation | ❌ Blocked |
The Fix Needed (Upstream)
The resolution code needs to include plugin-registered providers in the candidate list, not just bundled ones. The getHasCustomWebSearchRisk check exists but seems too conservative — it's designed to avoid loading plugin runtimes unnecessarily, but it ends up excluding all custom providers.
───
Current State
• qwen-proxy runs on 6969, works via direct curl
• Plugin is installed, loads without errors
• But OpenClaw's web search still uses its built-in provider
• The integration between registerWebSearchProvider() and the config resolver is broken for non-bundled providers
analyze everything i had sent you then tell me how to proceed
@random shell
I’m here 🦐
If you want, send the search API/service you want to plug in, for example SearXNG, SerpAPI, your own endpoint, etc., and I’ll turn this into a concrete minimal plugin example.
i sent you everything i had to send did you read my previous messages?
Yes, I did. Sorry, I wasn’t trying to make you repeat yourself.
What I have from this thread is just:
“How can we create a custom web search tool or provider to add to openclaw”
So the direct answer is:
- if you want a new standalone search tool, make a plugin and use
api.registerTool(...) - if you want your backend to power the built in
web_searchtool, make a plugin and useapi.registerWebSearchProvider(...)
Minimum pieces for the provider route:
-
openclaw.plugin.json- add
contracts.webSearchProviders: ["your-provider-id"]
- add
-
index.ts
definePluginEntry(...)api.registerWebSearchProvider({... createTool() ...})
-
config
- provider key goes under
plugins.entries.<pluginId>.config.webSearch.* - select it with
tools.web.search.provider: "<your-provider-id>"
- provider key goes under
-
install
openclaw plugins install ./your-pluginopenclaw gateway restart
So yes, I read your message, and you did send enough to answer in general. I only asked for an API name because the next step would be writing the exact plugin skeleton for a specific backend.
If you want, I can now give you a complete minimal provider plugin template with placeholder HTTP fetch code, no extra info needed.
o: 📋 Complete Implementation & Issue Report
───
What We Built
Goal
Replace OpenClaw's web search backend with qwen-proxy's free web search API (1000 req/day) so the web_search tool calls our local qwen-proxy instead of the current provider.
Implementation
qwen-proxy service (✅ Working)
• Installed via npm install -g qwen-proxy
• Running headless on http://localhost:6969/
• 1 Qwen OAuth account, healthy, auto-refreshing tokens
• POST /v1/web/search returns structured results with reranking
• Auto-start via crontab @reboot
• Status: Working perfectly. Tested and verified.
OpenClaw plugin (❌ Not Working)
• Created at ~/.openclaw/extensions/qwen-search/
• 3 files: package.json, openclaw.plugin.json, index.js
• Uses api.registerWebSearchProvider() from the Plugin SDK
• Configured like DuckDuckGo (the simplest bundled provider)
• autoDetectOrder: 150, requiresCredential: false
• Plugin shows as loaded in openclaw plugins list
• tools.web.search.provider set to "qwen-search"
• Gateway restarted after every change
───
The Issue
Symptom
[WEB_SEARCH_PROVIDER_INVALID_AUTODETECT] tools.web.search.provider is "qwen-search".
Falling back to auto-detect precedence.
The web_search tool silently falls back to auto-detect (picks Perplexity/DuckDuckGo) and completely ignores our plugin.
Root Cause
OpenClaw's web search provider resolution has two paths:
Bundled providers — The 12 built-in ones (Brave, SearXNG, DuckDuckGo, Ollama, Perplexity, etc.) These are hardcoded in the OpenClaw binary and always available.
Plugin providers — Providers registered via api.registerWebSearchProvider(). These load at runtime.
The bug is in the config resolution layer. When you set tools.web.search.provider: "qwen-search", the resolution code does this:
Load all bundled providers
Check if any custom plugin providers exist (getHasCustomWebSearchRisk)
If no "risk" detected → use only bundled providers via resolveBundledWebSearchProvidersFromPublicArtifacts
Filter the list with searchCompatibilityOnlyPluginIds
Try to match "qwen-search" in the filtered list
Not found → INVALID_AUTODETECT → fall back to auto-detect
The critical filter (searchCompatibilityOnlyPluginIds) only includes bundled plugin IDs. Custom plugins are excluded from the candidate list entirely. So even though registerWebSearchProvider() successfully registers our provider in the plugin registry, the config resolution layer never sees it when matching the configured provider name.
Why DuckDuckGo Works But We Don't
DuckDuckGo is bundled in openclaw/dist/extensions/duckduckgo/ — it ships with the binary. Its provider ID is in the hardcoded searchCompatibilityOnlyPluginIds list. Our plugin is a global extension loaded from ~/.openclaw/extensions/ — it's never in the candidate list.
The WW-AI-Lab Plugin
I found @ww-ai-lab/openclaw-web-search which does the exact same thing (registers qwen + metaso providers). It requires >=2026.3.28. We're on 2026.4.14. It might work on their version, or they might hit the same wall — but it confirms the SDK interface exists and is meant to work. This is a bug in the resolution layer, not in our code.
What Works vs What Doesn't
| Component | Status | Detail |
|---|---|---|
| [4/15/2026 12:30 AM] Nero: | qwen-proxy service | ✅ Working |
| Plugin loading | ✅ Working | Shows as "loaded" in plugin list |
| registerWebSearchProvider() | ✅ Working | No errors during registration |
| Provider matching | ❌ Broken | Config resolver filters out non-bundled providers |
| tools.web.search.provider: "qwen-search" | ❌ Rejected | Falls back to auto-detect |
| [4/15/2026 12:30 AM] Nero: | Config schema validation | ❌ Blocked |
The Fix Needed (Upstream)
The resolution code needs to include plugin-registered providers in the candidate list, not just bundled ones. The getHasCustomWebSearchRisk check exists but seems too conservative — it's designed to avoid loading plugin runtimes unnecessarily, but it ends up excluding all custom providers.
───
Current State
• qwen-proxy runs on 6969, works via direct curl
• Plugin is installed, loads without errors
• But OpenClaw's web search still uses its built-in provider
• The integration between registerWebSearchProvider() and the config resolver is broken for non-bundled providers
analyze everything i had sent you then tell me how to proceed
I’m checking the exact error path in source so I don’t guess.
Yep, I read it, and I think you’re very close.
This looks less like “provider registration failed” and more like a runtime resolver fast-path bug.
What I think is happening:
- your plugin can be loaded
- but the web-tools resolver still takes the bundled-provider fast path
- so when it validates
tools.web.search.provider: "qwen-search", it only sees bundled providers, not your runtime plugin provider - then you get:
[WEB_SEARCH_PROVIDER_INVALID_AUTODETECT] tools.web.search.provider is "qwen-search". Falling back to auto-detect precedence.
The suspicious code path is in src/secrets/runtime-web-tools.ts:
hasCustomWebSearchPluginRisk(...)resolveBundledWebSearchProviders(...)
hasCustomWebSearchPluginRisk(...) only flips to the custom-plugin path if it sees a non-bundled plugin through things like:
plugins.load.pathsplugins.installsplugins.allow/plugins.denyplugins.entries
So a plugin that is just sitting in ~/.openclaw/extensions/qwen-search/ can be discovered/loaded, but still not trip that “custom web search plugin” check.
Most likely workaround
Instead of relying on plain auto-discovery, make the plugin explicit.
1. Make sure the manifest declares the provider contract
In openclaw.plugin.json:
{
"id": "qwen-search",
"name": "Qwen Search",
"contracts": {
"webSearchProviders": ["qwen-search"]
}
}
2. Explicitly load/install it
Prefer one of these:
openclaw plugins install ~/.openclaw/extensions/qwen-search
or add a config load path:
{
plugins: {
load: {
paths: ["~/.openclaw/extensions/qwen-search"]
},
entries: {
"qwen-search": { enabled: true }
}
}
}
3. Keep the provider selection
{
tools: {
web: {
search: {
provider: "qwen-search"
}
}
}
}
Then restart:
openclaw gateway restart
Also, please send this first
openclaw --version
This area moved around a bit, so version matters a lot here.
If you want, paste these 3 things next and I’ll sanity-check the exact failure point:
openclaw --version
openclaw plugins inspect qwen-search
openclaw status --all
Docs:
And honestly, if your version is current, this does smell like a real bug worth filing at https://github.com/openclaw/openclaw/issues.
Thanks, and the version helps. On 2026.4.14, I think your overall conclusion is right: this looks like a resolver bug, not a bad registerWebSearchProvider() implementation.
One important correction though:
searchCompatibilityOnlyPluginIdsis not the main culprit for your explicittools.web.search.provider: "qwen-search"case- that filter only kicks in on the legacy compatibility path, basically when no explicit provider is set and it is trying to infer from old top-level config
For your case, the real tripwire is earlier:
hasCustomWebSearchPluginRisk(...)- then
resolveBundledWebSearchProviders(...)
If that “custom plugin risk” check stays false, OpenClaw keeps using the bundled public-artifact fast path, so your globally discovered plugin never makes it into the provider set that validates "qwen-search".
So I’d phrase the bug as:
explicit
web_searchprovider selection can ignore runtime-registered non-bundled providers when they are only auto-discovered as global extensions, because runtime web-tool resolution stays on the bundled fast path unless custom-plugin risk is detected from config/install metadata.
Why the WW-AI-Lab plugin may still work
If it was installed through openclaw plugins install ..., that would populate plugin install metadata and likely flip the custom-risk path.
A loose plugin under ~/.openclaw/extensions/... is different.
Best workaround right now
Do not rely on bare global auto-discovery for this one.
Use one of these instead:
openclaw plugins install ~/.openclaw/extensions/qwen-search
or explicit config:
{
plugins: {
load: {
paths: ["~/.openclaw/extensions/qwen-search"]
},
entries: {
"qwen-search": { enabled: true }
}
}
}
and keep:
{
tools: {
web: {
search: {
provider: "qwen-search"
}
}
}
}
Also make sure openclaw.plugin.json has:
{
"contracts": {
"webSearchProviders": ["qwen-search"]
}
}
Next step
Honestly, your writeup is already issue-ready. I’d file it here:
https://github.com/openclaw/openclaw/issues
If you want, I can help you trim it into a tight repro + expected/actual section before you post it.
If the workaround above still fails even with plugins.install or plugins.load.paths/plugins.entries, then I’d be very confident it is a deeper bug.
I read all of it. My honest take is:
stop debugging qwen-proxy and stop debugging registerWebSearchProvider().
Those parts look fine.
Your failure is higher up, in provider selection / config validation.
My read of the situation
What seems established already:
qwen-proxyworks- the plugin loads
registerWebSearchProvider()runs- the custom provider id is rejected before normal selection works
- OpenClaw falls back to bundled auto-detect
So the broken layer is very likely:
- config validation / provider-name acceptance
- runtime web-tools provider resolution
- bundled fast-path vs non-bundled plugin provider visibility
That is why this feels like an upstream bug, not a bad local implementation.
How I would proceed
Option 1, recommended: file an upstream issue now
I think this is the right move.
Your report is already strong. I’d tighten it into:
- Version:
openclaw 2026.4.14 - Expected: non-bundled plugin registered via
api.registerWebSearchProvider()is accepted astools.web.search.provider
- Actual: provider id is rejected /
[WEB_SEARCH_PROVIDER_INVALID_AUTODETECT]and OpenClaw falls back to bundled auto-detect - Repro: install non-bundled web-search plugin, confirm plugin loads, set
tools.web.search.providerto custom id, restart gateway - Likely area:
src/secrets/runtime-web-tools.ts
File here:
https://github.com/openclaw/openclaw/issues
Option 2, fastest workaround: make it a standalone tool instead of a web_search provider
If you need this working today, this is the fastest path.
- register
qwen_searchwithapi.registerTool(...) - disable built in web search:
{
tools: {
web: {
search: {
enabled: false
}
}
}
}
That bypasses the broken provider-selection path entirely.
Downside: it is not a drop-in backend replacement for built in web_search.
Upside: it should work right now.
Option 3, only if you need true drop-in replacement right now: patch/fork OpenClaw locally
If you absolutely need web_search itself to route through qwen-proxy now, you are probably in local patch territory.
The files I’d inspect first are:
src/secrets/runtime-web-tools.tssrc/secrets/runtime-web-tools.shared.tssrc/plugins/web-search-providers.runtime.ts
But I would only do this if you are okay carrying a local patch.
My recommendation
I’d do this in order:
- File the upstream bug
- Use a standalone
qwen_searchtool as the short-term workaround - only patch core locally if you really need built in
web_searchcompatibility
That gives you a working system now, without burning more time on a plugin path that currently looks broken.