Add DuckDuckGo Search to OpenClaw: Free Web Search Without API Keys

Step-by-step guide to adding DuckDuckGo search to OpenClaw (formerly Clawdbot/Moltbot). Replace Brave Search with a free alternative that doesn't require an API key.

Add DuckDuckGo Search to OpenClaw: Free Web Search Without API Keys

OpenClaw comes with Brave Search as the default web search provider. It works well, but you need an API key. If you want a free alternative that just works without signups, DuckDuckGo is the answer.

What You'll Need

  • OpenClaw installed on your server
  • Basic familiarity with editing TypeScript files
  • curl installed (available by default on most Linux systems)
  • No API key required for DuckDuckGo!

If you’re new to OpenClaw, check out our complete OpenClaw Setup Guide first.

Why DuckDuckGo Over Brave?

FeatureBrave SearchDuckDuckGo
API KeyRequired (free tier available)Not needed
Monthly CostFree tier + paid plans$0 forever
Rate LimitsPer-plan limitsAnti-bot risk
Setup ComplexityLowMedium (code modification)
Freshness FiltersYes (pd, pw, pm, py)No
ReliabilityHighMedium (HTML scraping)

DuckDuckGo uses HTML scraping, which means it can break if they change their page structure. But for personal use on OpenClaw, it’s a solid free option.

Overview of the Implementation

The approach uses DuckDuckGo’s HTML search endpoint (https://html.duckduckgo.com/html/) and parses results from the raw HTML. This is the same method used by many privacy-focused search tools.

Here’s what we’ll do:

  • Add DuckDuckGo to the list of search providers
  • Create a function to call DuckDuckGo HTML search via curl
  • Add an HTML parser to extract results
  • Update the provider resolution logic
  • Configure OpenClaw to use DuckDuckGo

Step 1: Add DuckDuckGo to Search Providers

Open the web search tool file:

nano ~/.openclaw/openclawd/src/agents/tools/web-search.ts

Find the SEARCH_PROVIDERS array near the top and add duckduckgo:

const SEARCH_PROVIDERS = ["brave", "perplexity", "grok", "duckduckgo"] as const;

Step 2: Add the DuckDuckGo Endpoint

Add the HTML search endpoint constant after the Brave endpoint:

const BRAVE_SEARCH_ENDPOINT = "https://api.search.brave.com/res/v1/web/search";
const DUCKDUCKGO_HTML_ENDPOINT = "https://html.duckduckgo.com/html/";

Step 3: Add Type Definitions

Add the DuckDuckGo result type before the parsing function:

type DuckDuckGoSearchResult = {
  title: string;
  url: string;
  description: string;
  siteName?: string;
};

Step 4: Create the HTML Parser

Add this function to parse DuckDuckGo’s HTML response:

function parseDuckDuckGoHtml(html: string): DuckDuckGoSearchResult[] {
  const results: DuckDuckGoSearchResult[] = [];
  const linkRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>([^<]*)<\/a>/gi;
  const snippetRegex = /<a[^>]*class="result__snippet"[^>]*>([^<]*(?:<[^>]*>[^<]*)*)<\/a>/gi;

  const links: { url: string; title: string }[] = [];
  let match;

  // Extract links and titles
  while ((match = linkRegex.exec(html)) !== null) {
    let url = match[1] ?? "";
    const title = (match[2] ?? "").trim();

    // DuckDuckGo redirects through their own URL - extract the real URL
    if (url.includes("uddg=")) {
      try {
        const parsed = new URL(url, "https://duckduckgo.com");
        const realUrl = parsed.searchParams.get("uddg");
        if (realUrl) {
          url = decodeURIComponent(realUrl);
        }
      } catch {
        // Keep original URL if parsing fails
      }
    }

    if (url && title && url.startsWith("http")) {
      links.push({ url, title });
    }
  }

  // Extract snippets
  const snippets: string[] = [];
  while ((match = snippetRegex.exec(html)) !== null) {
    const snippet = (match[1] ?? "").replace(/<[^>]*>/g, "").trim();
    snippets.push(snippet);
  }

  // Combine links with snippets
  for (let i = 0; i < links.length; i++) {
    const link = links[i];
    if (!link) {
      continue;
    }
    results.push({
      title: link.title,
      url: link.url,
      description: snippets[i] ?? "",
      siteName: resolveSiteName(link.url),
    });
  }

  return results;
}

Step 5: Create the Search Function

Add a function that performs the actual DuckDuckGo search using curl:

async function runDuckDuckGoSearch(params: {
  query: string;
  count: number;
  timeoutSeconds: number;
}): Promise<DuckDuckGoSearchResult[]> {
  const { execFileSync } = await import("child_process");

  const curlArgs = [
    "-s",
    "--max-time",
    String(params.timeoutSeconds),
    "-X",
    "POST",
    "-H",
    "Content-Type: application/x-www-form-urlencoded",
    "-H",
    "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
    "-H",
    "Accept: text/html",
    "-d",
    `q=${encodeURIComponent(params.query)}`,
    DUCKDUCKGO_HTML_ENDPOINT,
  ];

  try {
    const html = execFileSync("curl", curlArgs, {
      encoding: "utf-8",
      maxBuffer: 2 * 1024 * 1024,
      timeout: params.timeoutSeconds * 1000,
    });

    const allResults = parseDuckDuckGoHtml(html);

    // Check if we got results or just the homepage (anti-bot detection)
    if (allResults.length === 0 && html.includes("<title>") && !html.includes("at DuckDuckGo")) {
      throw new Error(
        "DuckDuckGo returned homepage instead of search results (possible anti-bot detection)"
      );
    }

    return allResults.slice(0, params.count);
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    throw new Error(`DuckDuckGo search failed: ${message}`, { cause: err });
  }
}

Step 6: Update Provider Resolution

Find the resolveSearchProvider function and add DuckDuckGo support:

function resolveSearchProvider(search?: WebSearchConfig): (typeof SEARCH_PROVIDERS)[number] {
  const raw =
    search && "provider" in search && typeof search.provider === "string"
      ? search.provider.trim().toLowerCase()
      : "";
  if (raw === "perplexity") {
    return "perplexity";
  }
  if (raw === "grok") {
    return "grok";
  }
  if (raw === "duckduckgo" || raw === "ddg") {
    return "duckduckgo";
  }
  if (raw === "brave") {
    return "brave";
  }
  return "brave"; // Default
}

Step 7: Add DuckDuckGo to the Main Search Function

In the runWebSearch function, add a case for DuckDuckGo before the Brave fallback:

async function runWebSearch(params: {
  // ... existing params
}): Promise<Record<string, unknown>> {
  // ... existing cache logic

  const start = Date.now();

  // ... existing Perplexity code...

  if (params.provider === "duckduckgo") {
    const ddgResults = await runDuckDuckGoSearch({
      query: params.query,
      count: params.count,
      timeoutSeconds: params.timeoutSeconds,
    });
    const payload = {
      query: params.query,
      provider: params.provider,
      count: ddgResults.length,
      tookMs: Date.now() - start,
      results: ddgResults,
    };
    writeCache(SEARCH_CACHE, cacheKey, payload, params.cacheTtlMs);
    return payload;
  }

  // ... existing Brave code...
}

Step 8: Update Tool Description

Modify the createWebSearchTool function to include DuckDuckGo in the description:

const description =
  provider === "perplexity"
    ? "Search the web using Perplexity Sonar (direct or via OpenRouter). Returns AI-synthesized answers with citations from real-time web search."
    : provider === "grok"
      ? "Search the web using xAI Grok. Returns AI-synthesized answers with citations from real-time web search."
      : provider === "duckduckgo"
        ? "Search the web using DuckDuckGo. Free search without API key requirements. Returns titles, URLs, and snippets."
        : "Search the web using Brave Search API. Supports region-specific and localized search via country and language parameters. Returns titles, URLs, and snippets for fast research.";

Step 9: Skip API Key Check for DuckDuckGo

In the execute function, modify the API key check:

execute: async (_toolCallId, args) => {
  // ... existing code...

  if (!apiKey && provider !== "duckduckgo") {
    return jsonResult(missingSearchKeyPayload(provider));
  }

  // ... rest of the code...
}

Step 10: Configure OpenClaw

Edit your OpenClaw configuration:

nano ~/.openclaw/config.json

Set DuckDuckGo as your search provider:

{
  "tools": {
    "web": {
      "search": {
        "provider": "duckduckgo"
      }
    }
  }
}

Or use the CLI:

openclaw configure --section web

Step 11: Rebuild and Restart

After making all the code changes, rebuild OpenClaw:

cd ~/.openclaw/openclawd
npm run build

Restart the gateway:

openclaw gateway restart

Testing Your Setup

Send a search query to OpenClaw through your messaging channel:

“Search for the latest Node.js release”

You should see results from DuckDuckGo without any API key configuration.

Alternative: Use Community Patch

If you don’t want to manually modify the code, there’s a community fork with DuckDuckGo already implemented:

git clone https://github.com/jokelord/openclaw-local-model-tool-calling-patch.git
cd openclaw-local-model-tool-calling-patch

The DuckDuckGo implementation is in openclawd-2026.2.3/src/agents/tools/web-search.ts.

View Community Patch

Limitations to Know

DuckDuckGo Limitations

  • No freshness filters: Unlike Brave, you can’t filter by past day/week/month
  • Anti-bot detection: Heavy usage may trigger blocks
  • HTML parsing: Could break if DuckDuckGo changes their page structure
  • No country/language filters: Less granular control than Brave API

For production use or heavy search loads, consider using Brave Search with an API key instead. The free tier is generous enough for personal OpenClaw use.

Comparing Search Providers

Pros:

  • Official API with guaranteed stability
  • Freshness filters (past day, week, month, year)
  • Country and language targeting
  • Higher rate limits on paid plans

Cons:

  • Requires API key signup
  • Free tier has limits
  • Paid plans for heavy usage

Best for: Production use, teams, heavy search needs

Pros:

  • No API key required
  • Free forever
  • Privacy-focused
  • Easy setup (once code is modified)

Cons:

  • HTML scraping can break
  • Anti-bot detection risk
  • No freshness filters
  • Less reliable for heavy use

Best for: Personal use, testing, privacy enthusiasts

Pros:

  • AI-synthesized answers
  • Built-in citations
  • Real-time web access
  • Direct answers, not just links

Cons:

  • Requires API key (more expensive)
  • Different output format
  • May be overkill for simple searches

Best for: Research tasks, comprehensive answers

Troubleshooting

”DuckDuckGo returned homepage instead of search results”

This means anti-bot detection kicked in. Try:

  1. Reduce search frequency
  2. Add delays between searches
  3. Consider rotating user agents

”curl: command not found”

Install curl:

apt install curl -y  # Ubuntu/Debian

Results Are Empty

Check the HTML structure - DuckDuckGo may have changed their page layout. The parser looks for:

  • Links with class result__a
  • Snippets with class result__snippet

TypeScript Compilation Errors

Make sure you’ve added all the type definitions and imported execFileSync correctly:

const { execFileSync } = await import("child_process");
Frequently Asked Questions

Why use curl instead of fetch?

DuckDuckGo’s HTML endpoint works better with curl’s default headers and behavior. Node’s fetch can trigger different responses. Curl is also more likely to be cached and efficient.

Can I use both Brave and DuckDuckGo?

Yes! Switch providers in your config anytime, or modify the code to support fallback providers.

Is this against DuckDuckGo’s Terms of Service?

This uses their public HTML search, which is accessible to anyone. For heavy commercial use, consider their official API or use Brave Search instead.

How do I switch back to Brave?

Just change the provider in your config:

{
  "tools": {
    "web": {
      "search": {
        "provider": "brave",
        "apiKey": "YOUR_BRAVE_API_KEY"
      }
    }
  }
}

Will this work with future OpenClaw versions?

The implementation may need updates if OpenClaw changes their web search architecture. Watch the official repo for changes to web-search.ts.

Adding DuckDuckGo to OpenClaw gives you a free, privacy-focused search option without API key management. It’s perfect for personal use and testing. For production or team deployments, Brave Search with its official API is still the recommended choice.

If you’re exploring alternatives to OpenClaw that also support web search, check our NanoClaw deploy guide (container-isolated Claude agents) and NullClaw deploy guide (678 KB Zig binary with 22+ providers).

For more OpenClaw tips, see our complete OpenClaw Setup Guide, OpenClaw alternatives, best OpenClaw dashboards if you want a UI for monitoring sessions and costs, the OpenClaw security guide for hardening your instance against CVE-2026-25253 and other vulnerabilities, and running OpenClaw with Ollama if you want to pair free local models with DuckDuckGo for a fully self-hosted setup.