The Direct Flag Mapping
Every cURL flag has a fetch equivalent. Memorize this table and conversion becomes mechanical:
-X POST→{ method: "POST" }-H "Authorization: Bearer xyz"→headers: { Authorization: "Bearer xyz" }-d '{"name":"John"}'→body: JSON.stringify({ name: "John" })-u alice:pw→headers: { Authorization: "Basic " + btoa("alice:pw") }-b "session=abc"→credentials: "include"(browser handles cookies)-F "file=@photo.jpg"→ FormData with appended File object
Basic GET
cURL: curl https://api.example.com/users
Fetch: fetch("https://api.example.com/users").then(r => r.json())
That's it. No method needed (GET is default), no body, no headers. The .then(r => r.json()) is the part people forget — fetch does not auto-parse JSON like axios does.
POST with JSON Body
cURL:
curl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d '{"name":"John","email":"j@x.com"}'Fetch:
await fetch("https://api.example.com/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "John", email: "j@x.com" })
});The two tripwires: forgetting JSON.stringify (fetch will send the literal string [object Object]) and forgetting the Content-Type header (the server will refuse to parse the body).
Authentication
Bearer tokens are trivial — copy the header. Basic auth (-u user:pass) needs base64 encoding:
headers: { Authorization: "Basic " + btoa("alice:secret")
}For OAuth flows, store the token in memory (not localStorage) and add it to each request. Never put long-lived API keys in client-side fetch — anyone can read them via DevTools.
The CORS Wall
cURL doesn't care about CORS. Browsers do. If your converted fetch fails with "blocked by CORS policy" while the cURL works, the API server is not sending the right headers for your origin. Three options: (1) ask the API owner to add your origin to Access-Control-Allow-Origin, (2) proxy the request through your own backend, or (3) move the call server-side entirely. There is no client-side workaround — disabling CORS in the browser only affects your machine.
File Uploads
cURL: curl -F "file=@photo.jpg" https://api.example.com/upload
Fetch:
const formData = new FormData();
formData.append("file", fileInput.files[0]); await fetch("https://api.example.com/upload", { method: "POST", body: formData // do NOT set Content-Type — browser handles boundary
});The Content-Type rule is critical. multipart/form-data requires a boundary string that the browser generates; if you set the header manually, you'll send the wrong boundary and the server will reject the upload.
Error Handling
Unlike most HTTP libraries, fetch does NOT throw on 4xx or 5xx responses. It only throws on network failures. Always check response.ok:
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();Test Your Conversion
After converting, paste the fetch into a browser console on the target origin and run it. If it fails, the cURL response and the fetch error message together tell you which flag was missed. For complex requests, use the cURL examples reference as a checklist.