Web Fetch Tool: HTTP GET with Readable Content Extraction
Learn how to use the web_fetch tool to retrieve web pages and convert HTML to markdown or plain text. Ideal for developers needing simple, JavaScript-free fetching without browser automation.
Read this when
- You want to fetch a URL and extract readable content
- You need to configure web_fetch or its Firecrawl fallback
- You want to understand web_fetch limits and caching
web_fetch performs a straightforward HTTP GET and converts the response into readable content, turning HTML into markdown or plain text. JavaScript is never executed. For pages that rely heavily on JS or sit behind authentication, turn to the Web Browser tool instead.
Quick start
This tool is on by default and needs no setup:
await web_fetch({ url: "https://example.com/article" });
Tool parameters
-
url(string, required), The target URL to retrieve. Onlyhttp(s)is supported. -
extractMode(markdown' | 'text, default: markdown), Determines the output format after the main content has been extracted. -
maxChars(number), Caps the output at this character count. The value is clamped totools.web.fetch.maxCharsCap.
Result
web_fetch yields a closed structured result carrying these fields:
- Request details:
url,finalUrl,status,extractMode, andextractor - Response details that appear only when available:
contentType,title, andwarning - Wrapped content details:
externalContent,truncated,length,rawLength,fetchedAt,tookMs, andtext cached: trueshows up when the response came from cachespill: { path, chars, truncated? }appears when truncated content was saved to a private temporary file;truncatedis included only if that file holds partial source content
length measures the wrapped text size. rawLength measures the extracted content size prior to external-content wrapping.
How it works
Fetch
Issues an HTTP GET using a Chrome-like User-Agent and the Accept-Language
header. Private and internal hostnames are blocked, and redirects are re-validated.
Extract
Applies Readability to the HTML response to pull out the main content.
Fallback (optional)
When Readability fails and a fetch provider is configured, the request is retried through that provider, such as Firecrawl's bot-circumvention mode.
Cache
Responses are cached for 15 minutes (adjustable) to avoid fetching the same URL repeatedly.
Progress updates
web_fetch logs a public progress line only if the fetch remains unresolved after five seconds:
Fetching page content...
Fast cache hits and quick network replies complete before the timer triggers, so no progress line is ever shown. Cancelling the call stops the timer. The progress line exists purely as channel UI state and never carries any fetched page content.
Config
{
tools: {
web: {
fetch: {
enabled: true, // default: true
provider: "firecrawl", // optional; omit for auto-detect
maxChars: 20000, // default output chars; capped by maxCharsCap
maxCharsCap: 20000, // hard cap for maxChars param
maxResponseBytes: 750000, // max download size before truncation (32000-10000000)
timeoutSeconds: 30,
cacheTtlMinutes: 15,
maxRedirects: 3,
useTrustedEnvProxy: false, // let a trusted HTTP(S) env proxy resolve DNS
readability: true, // use Readability extraction
userAgent: "Mozilla/5.0 ...", // override User-Agent
headers: {
// optional; every value is treated as sensitive
"X-Routing-Target": "staging",
},
ssrfPolicy: {
dangerouslyAllowPrivateNetwork: false, // broad private-network opt-in; keep false by default
allowedHostnames: ["internal.example"], // narrow exact host exception
allowRfc2544BenchmarkRange: true, // opt-in for trusted fake-IP proxies using 198.18.0.0/15
allowIpv6UniqueLocalRange: true, // opt-in for trusted fake-IP proxies using fc00::/7
},
},
},
},
}
Firecrawl fallback
Should Readability extraction come up short, web_fetch can switch to Firecrawl for bot-circumvention and improved extraction:
{
tools: {
web: {
fetch: {
provider: "firecrawl", // optional; omit for auto-detect from available credentials
},
},
},
plugins: {
entries: {
firecrawl: {
enabled: true,
config: {
webFetch: {
// apiKey: "fc-...", // optional; omit for keyless starter access
baseUrl: "https://api.firecrawl.dev",
onlyMainContent: true,
maxAgeMs: 172800000, // cache duration (2 days)
timeoutSeconds: 60,
},
},
},
},
},
}
plugins.entries.firecrawl.config.webFetch.apiKey is optional and works with SecretRef objects.
Older tools.web.fetch.firecrawl.* settings are automatically migrated to
plugins.entries.firecrawl.config.webFetch through openclaw doctor --fix.
Note
If a Firecrawl API-key SecretRef is configured but cannot be resolved and no
FIRECRAWL_API_KEYenv fallback exists, the gateway fails fast at startup.
Note
Firecrawl
baseUrloverrides are restricted: hosted traffic is locked tohttps://api.firecrawl.dev; self-hosted overrides must point at private or internal endpoints, andhttp://is allowed only for those private targets.
Behavior as of the current runtime:
- Setting
tools.web.fetch.providerexplicitly chooses the fetch fallback provider. - When
provideris not provided, OpenClaw picks the first available web-fetch provider automatically based on the credentials it finds. Outside a sandbox,web_fetchmay rely on installed plugins that declarecontracts.webFetchProvidersand register a matching provider during runtime. The official Firecrawl plugin currently serves as that fallback. - Within a sandbox,
web_fetchcalls support both bundled providers and installed ones whose official npm or ClawHub provenance has been verified. As of now, that includes only the official Firecrawl plugin; third-party external fetch plugins remain disallowed. - With Readability turned off,
web_fetchgoes directly to the chosen provider fallback. When no provider exists, it fails closed.
Custom request headers
Use tools.web.fetch.headers if your setup needs extra request metadata on
outbound fetches, like a routing or service-injection header that directs traffic
toward a gateway you manage.
{
tools: {
web: {
fetch: {
headers: {
"X-Routing-Target": "${WEB_FETCH_ROUTING_TARGET}",
},
},
},
},
}
Warning
Any value you configure is considered sensitive and gets redacted from exposed config and debug captures. Those headers are still transmitted to every initial URL that
web_fetchrequests, and the model decides that URL. Only set credential headers when that matches your intended trust boundary.
Useful behavior to know:
- Values are plain strings and accept
${VAR}environment substitution just like any other config string. Structured SecretRef values are not supported. - Headers affect only the direct
web_fetchrequest. Provider fallbacks like Firecrawl hit their own API and never see these headers. - Validation happens when the request is assembled, not at config load, so a single bad entry is discarded while the rest still apply. Config load stays permissive on purpose: a fail-closed validation error from one header-name typo would take down the entire surface. Each dropped entry is logged by name.
- Names that get dropped:
Accept,Accept-Language, andUser-Agentbelong to the fetch and readability contract. For the user agent, usetools.web.fetch.userAgent.- Framing and hop-by-hop names like
Content-Length,Transfer-Encoding,Connection, andUpgrade, which a request either rejects outright or ignores. - Names that are not valid HTTP tokens, such as
"X Routing Target".
- Values that get dropped: bytes a request cannot carry (CR, LF, NUL, or any
character above
U+00FF). Missing environment variables are flagged by config loading; the global$${VAR}escape stays available when the literal${VAR}text is meant to appear. - Two entries whose names differ only by case collapse to the later entry, so a request never carries a comma-joined value that the receiving gateway cannot parse. The dropped name is logged without either value. If the later entry is unusable, neither value goes out.
- Rejection occurs before the cache key is computed, so the key always matches the bytes actually sent: changing a header that is really sent partitions the fetch cache, while adding one that gets dropped does not.
- When a redirect crosses origins, the guarded-fetch safe allowlist applies.
Routing headers outside that list are dropped; standard safe headers such as
Cache-Control,Content-Type, andRangeare kept.
Trusted env proxy
If your deployment needs web_fetch to route through a trusted outbound
HTTP(S) proxy, set tools.web.fetch.useTrustedEnvProxy: true.
In this mode, OpenClaw still runs hostname-based SSRF checks before sending the request, but it lets the proxy resolve DNS instead of doing local DNS pinning. Enable this only when the proxy is operator-controlled and enforces outbound policy after DNS resolution.
Note
If no HTTP(S) proxy env var is set, or the target host is excluded by
NO_PROXY,web_fetchfalls back to the normal strict path with local DNS pinning.
Limits and safety
maxCharsis clamped totools.web.fetch.maxCharsCap(default20000)- Response body is capped at
maxResponseBytes(default750000, clamped to 32000-10000000) before parsing; oversized responses are truncated with a warning - Private/internal hostnames are blocked
tools.web.fetch.ssrfPolicy.allowedHostnamesallows exact trusted hosts while leaving other private/internal targets blockedtools.web.fetch.ssrfPolicy.dangerouslyAllowPrivateNetworkbroadly permits private-network targets; enable it only when model-selected URLs are trusted in this deploymenttools.web.fetch.ssrfPolicy.allowRfc2544BenchmarkRangeandtools.web.fetch.ssrfPolicy.allowIpv6UniqueLocalRangeare narrow opt-ins for trusted fake-IP proxy stacks; leave them unset unless your proxy owns those synthetic ranges and enforces its own destination policy- Redirects are checked and limited by
maxRedirects(default3) tools.web.fetch.headersvalues are redacted from exposed config and debug captures, sent to the initial fetched host, and retained on redirects only when the existing guarded-fetch policy allows themuseTrustedEnvProxyis an explicit opt-in and should only be enabled for operator-controlled proxies that still enforce outbound policy after DNS resolutionweb_fetchis best-effort -- some sites need the Web Browser
Tool profiles
If you use tool profiles or allowlists, add web_fetch or group:web:
{
tools: {
allow: ["web_fetch"],
// or: allow: ["group:web"] (includes web_fetch, web_search, and x_search)
},
}
Related
- Web Search -- search the web with multiple providers
- Web Browser -- full browser automation for JS-heavy sites
- Firecrawl -- Firecrawl search and scrape tools