Fetch Client
fetch is global. It reads method / headers / body from the init object and returns a Promise<Response>. Everything else in init (signal, timeout, redirect) is ignored. Method names are exact-case standard tokens — lowercase "post" is sent as GET (see Troubleshooting).
Quick look
Modules vs scripts
ff treats a file as an ES module only when its name ends in .mjs or
its first code is import/export. A plain .js file runs as a classic
script, where top-level await is a syntax error — so the runnable
examples on this page wrap async code in (async () => { ... })(). Save
any of them with a .mjs extension if you'd rather use top-level await
as written.
GET with headers
Check res.ok — HTTP error statuses resolve, they don't throw:
Caution:
fetchonly throws on network failures (DNS, refused connection, too many redirects). A404or500still resolves — always checkres.okorres.status.
POST JSON
string bodies — and URLSearchParams, which coerces to its query string — reach the wire intact. Everything else is coerced with JS String() semantics: a FormData or Blob arrives as "[object Object]", an ArrayBuffer as "[object ArrayBuffer]", a Uint8Array([104, 105]) as "104,105".
Known issue:
fetchdoes not multipart-encodeFormDataor send binary bodies as bytes. Build the payload yourself:
Reading the response
Every reader works on Response (and Request). Each body reads once — after that bodyUsed is true:
Redirects
Redirects are followed automatically, up to 5 hops. After that fetch throws Too many redirects:
There is no redirect: "manual" mode — the init key is ignored.
HTTPS trust
Public CAs work out of the box. For private/self-signed CAs:
Note:
--cais not accepted byff start. UseFF_CA_FILE(orFF_CERT) there.
Practical example: synced mirror
Fetch a list, then POST each item onward, checking every status:
Reference: limits
Troubleshooting
404 resolves instead of throwing — by design. Check res.ok / res.status after every call.
Lowercase method: "post" arrives as GET — method tokens match exactly; anything unrecognized goes out as GET (the body is still attached). Use "POST".
FormData / Blob / Uint8Array body arrives mangled — bodies are string-coerced, not serialized ("[object Object]", "104,105"). Send string, URLSearchParams, or a hand-built multipart string instead.
Caught fetch error has no .message — network failures reject with plain strings ("Network error", "Invalid URL", "Too many redirects"). Log String(e).
Too many redirects — the chain exceeded 5 hops. Inspect res.url on the last good hop or fix the redirect loop server-side.
TLS verify fails on a private CA — pass --ca (scripts) or FF_CA_FILE (ff start). Confirm the PEM path is readable.
Hanging with 17+ parallel fetches — the 16-slot pool is full. Await in batches or raise concurrency by sequencing.
