#How can we create a custom web search tool or provider to add to openclaw

39 messages · Page 1 of 1 (latest)

iron raft
#

How can we create a custom web search tool or provider to add to openclaw

#

@random shell

random shellBOT
#

Yep, best path is a plugin.

If you want:

  1. a brand new tool name, use api.registerTool(...)
  2. a new backend for built in web_search, use api.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" },
iron raft
#

[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

  1. 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.

  1. 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:

  1. Bundled providers — The 12 built-in ones (Brave, SearXNG, DuckDuckGo, Ollama, Perplexity, etc.) These are hardcoded in the OpenClaw binary and always available.
  2. 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:

random shellBOT
#
        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 older createPluginBackedWebSearchProvider(...) path is deprecated.
iron raft
#
  1. Load all bundled providers
  2. Check if any custom plugin providers exist (getHasCustomWebSearchRisk)
  3. If no "risk" detected → use only bundled providers via resolveBundledWebSearchProvidersFromPublicArtifacts
  4. Filter the list with searchCompatibilityOnlyPluginIds
  5. Try to match "qwen-search" in the filtered list
  6. 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

random shellBOT
#

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.

iron raft
#

i sent you everything i had to send did you read my previous messages?

random shellBOT
#

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_search tool, make a plugin and use api.registerWebSearchProvider(...)

Minimum pieces for the provider route:

  1. openclaw.plugin.json

    • add contracts.webSearchProviders: ["your-provider-id"]
  2. index.ts

#
  • definePluginEntry(...)
    • api.registerWebSearchProvider({... createTool() ...})
  1. config

    • provider key goes under plugins.entries.<pluginId>.config.webSearch.*
    • select it with tools.web.search.provider: "<your-provider-id>"
  2. install

    • openclaw plugins install ./your-plugin
    • openclaw 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.

iron raft
#

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

random shellBOT
#

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.paths
  • plugins.installs
  • plugins.allow / plugins.deny
  • plugins.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:

#

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:

  • searchCompatibilityOnlyPluginIds is not the main culprit for your explicit tools.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_search provider 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-proxy works
  • 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 as tools.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.provider to 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_search with api.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.ts
  • src/secrets/runtime-web-tools.shared.ts
  • src/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:

  1. File the upstream bug
  2. Use a standalone qwen_search tool as the short-term workaround
  3. only patch core locally if you really need built in web_search compatibility

That gives you a working system now, without burning more time on a plugin path that currently looks broken.