Convert curl to JavaScript fetch: Complete Guide
curl is the universal tool for testing HTTP requests from the command line. But when you need to replicate that same request in a browser or Node.js application, you need the fetch() API. This guide covers every curl flag and its fetch equivalent, with real copy-paste examples.
Why Developers Need to Convert curl to fetch
The workflow is common: you get a curl command from an API vendor's documentation, from a colleague, or from a curl -v trace while debugging. Now you need to reproduce that request in JavaScript. The syntax is completely different, and the mapping is not always obvious.
curl and fetch handle the same concepts - HTTP methods, headers, request bodies, authentication - but in different ways:
- curl uses command-line flags like
-X,-H,-d,-u - fetch uses a JavaScript options object with
method,headers,body - curl handles redirects and SSL automatically; fetch requires explicit configuration for some of those behaviors
- curl outputs to stdout; fetch returns a Promise that resolves to a
Responseobject
Understanding the mapping between the two is a core skill for any developer working with HTTP APIs.
The Basic Structure: curl vs. fetch
Before diving into specific patterns, here is the high-level structural comparison:
# curl: everything is flags on one line
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN" \
-d '{"name":"Alice","email":"alice@example.com"}'
// fetch: options object passed as second argument
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer TOKEN'
},
body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' })
});
const data = await response.json();
Step-by-Step Conversion Patterns
1. Simple GET Request
A plain GET request is the simplest case. curl defaults to GET, and so does fetch:
# curl GET
curl https://api.example.com/users
// fetch GET (method is optional for GET)
const response = await fetch('https://api.example.com/users');
const data = await response.json();
2. GET with Query Parameters
Query parameters are part of the URL in both curl and fetch. The cleanest approach in JavaScript is to use URLSearchParams:
# curl with query params
curl "https://api.example.com/users?page=2&limit=20"
// fetch with URLSearchParams (cleaner for dynamic params)
const params = new URLSearchParams({ page: 2, limit: 20 });
const response = await fetch(`https://api.example.com/users?${params}`);
const data = await response.json();
3. POST with JSON Body
The most common conversion. The curl -d flag maps to the fetch body option. Critical: always set Content-Type: application/json when sending JSON - fetch does not infer this automatically:
# curl POST JSON
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name":"Alice","role":"admin"}'
// fetch POST JSON
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice', role: 'admin' })
});
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const newUser = await response.json();
4. Custom Headers: -H Flag
Each curl -H flag becomes a key-value pair in the fetch headers object. Multiple headers are straightforward:
# curl with multiple headers
curl https://api.example.com/data \
-H "Authorization: Bearer eyJhbGc..." \
-H "X-Request-ID: abc-123" \
-H "Accept: application/json"
// fetch with multiple headers
const response = await fetch('https://api.example.com/data', {
headers: {
'Authorization': 'Bearer eyJhbGc...',
'X-Request-ID': 'abc-123',
'Accept': 'application/json'
}
});
You can also use the Headers constructor for programmatic header building:
const headers = new Headers();
headers.append('Authorization', `Bearer ${token}`);
headers.append('X-Request-ID', crypto.randomUUID());
const response = await fetch(url, { headers });
5. Basic Authentication: -u Flag
curl's -u username:password flag sends HTTP Basic Auth. In fetch, you must construct the Base64-encoded Authorization header manually:
# curl basic auth
curl -u alice:secret123 https://api.example.com/protected
// fetch basic auth
const credentials = btoa('alice:secret123'); // base64 encode
const response = await fetch('https://api.example.com/protected', {
headers: {
'Authorization': `Basic ${credentials}`
}
});
6. PUT and PATCH Requests
curl's -X PUT and -X PATCH flags map directly to the fetch method option:
# curl PUT
curl -X PUT https://api.example.com/users/42 \
-H "Content-Type: application/json" \
-d '{"name":"Alice Updated"}'
# curl PATCH
curl -X PATCH https://api.example.com/users/42 \
-H "Content-Type: application/json" \
-d '{"name":"Alice Updated"}'
// fetch PUT
const response = await fetch('https://api.example.com/users/42', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice Updated' })
});
// fetch PATCH (same pattern, different method)
const response = await fetch('https://api.example.com/users/42', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice Updated' })
});
7. DELETE Request
# curl DELETE
curl -X DELETE https://api.example.com/users/42
// fetch DELETE
const response = await fetch('https://api.example.com/users/42', {
method: 'DELETE'
});
// 204 No Content is common - don't call .json() on empty body
if (response.status === 204) {
console.log('Deleted successfully');
}
8. Form Data: -F and --data-urlencode
curl's -F flag sends multipart form data. In fetch, use FormData. For URL-encoded forms (--data-urlencode), use URLSearchParams as the body:
# curl multipart form
curl -X POST https://api.example.com/upload \
-F "file=@/path/to/photo.jpg" \
-F "caption=My photo"
# curl URL-encoded form
curl -X POST https://api.example.com/login \
--data-urlencode "username=alice" \
--data-urlencode "password=secret"
// fetch multipart form
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('caption', 'My photo');
// Do NOT set Content-Type header - browser sets it with boundary automatically
const response = await fetch('https://api.example.com/upload', {
method: 'POST',
body: formData
});
// fetch URL-encoded form
const body = new URLSearchParams({ username: 'alice', password: 'secret' });
const response = await fetch('https://api.example.com/login', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString()
});
9. Follow Redirects
curl follows redirects by default with -L. fetch also follows redirects by default. To disable redirect following:
# curl: disable redirect following
curl --max-redirs 0 https://example.com
// fetch: disable redirect following
const response = await fetch('https://example.com', {
redirect: 'manual' // 'follow' (default), 'manual', or 'error'
});
10. Setting a Timeout
curl has --max-time and --connect-timeout. fetch has no built-in timeout, but you can use AbortController:
# curl with 5 second timeout
curl --max-time 5 https://api.example.com/slow-endpoint
// fetch with timeout via AbortController
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 seconds
try {
const response = await fetch('https://api.example.com/slow-endpoint', {
signal: controller.signal
});
const data = await response.json();
clearTimeout(timeoutId);
return data;
} catch (err) {
if (err.name === 'AbortError') {
throw new Error('Request timed out after 5 seconds');
}
throw err;
}
Format & Validate JSON Responses Instantly
Paste the JSON from your API response and get it formatted, validated, and syntax-highlighted. Free, runs entirely in your browser.
Open JSON FormatterError Handling in fetch
One of the most important differences between curl and fetch: fetch does not throw on HTTP error status codes. A 404 or 500 response resolves normally - you must check response.ok or response.status explicitly:
// Common mistake: this does NOT catch 404 or 500 errors
try {
const response = await fetch('/api/data');
const data = await response.json(); // runs even on 404!
} catch (err) {
// Only catches network errors, not HTTP errors
}
// Correct pattern
async function fetchWithErrorHandling(url, options) {
const response = await fetch(url, options);
if (!response.ok) {
// response.ok is true for 200-299
const errorBody = await response.text();
throw new Error(`HTTP ${response.status}: ${errorBody}`);
}
return response.json();
}
Complete curl to fetch Flag Mapping
-X POST→method: 'POST'-H "Key: Value"→headers: { 'Key': 'Value' }-d 'body'→body: 'body'-u user:pass→headers: { 'Authorization': 'Basic ' + btoa('user:pass') }-F field=value→body: new FormData()--data-urlencode→body: new URLSearchParams()-L(follow redirects) →redirect: 'follow'(default)--max-redirs 0→redirect: 'manual'--max-time N→AbortControllerwithsetTimeout-k(insecure SSL) → Not available in browser fetch (by design)--compressed→ Handled automatically by browser fetch-b "cookie=value"→headers: { 'Cookie': 'cookie=value' }-c cookiejar→credentials: 'include'(for browser cookies)
Node.js Considerations
fetch is built into browsers natively. In Node.js:
- Node.js 18+: Native
fetchis available globally (stable since Node 21) - Node.js 16 and earlier: Use the
node-fetchpackage or Axios - node-fetch v3+: ESM only - use
import fetch from 'node-fetch' - SSL bypass: In Node.js you can use a custom
https.AgentwithrejectUnauthorized: falsefor the equivalent of curl's-kflag (never in production)
// Node.js 18+ - native fetch
const response = await fetch('https://api.example.com/data');
// Node.js 16 and earlier
import fetch from 'node-fetch';
const response = await fetch('https://api.example.com/data');
FAQ
Does fetch send cookies automatically?
Only for same origin requests. For cross origin requests, set credentials: 'include' in the fetch options. This is the equivalent of curl's -c/-b cookie jar flags, but using the browser's cookie store.
Why does my fetch request fail in the browser but work in curl?
This is almost always a CORS issue. curl bypasses CORS entirely because it is not a browser. In the browser, cross origin requests must have proper Access-Control-Allow-Origin headers from the server. The fix is server side, not in your fetch code.
How do I send a raw body (not JSON) with fetch?
Pass the string directly as the body and set the appropriate Content-Type header. For binary data, pass a Blob, ArrayBuffer, or ReadableStream as the body.
Does fetch support HTTP/2?
Yes, modern browsers use HTTP/2 automatically when the server supports it. curl also uses HTTP/2 with --http2, but in fetch this is transparent - you do not need to configure it.
How do I see the request headers fetch is sending (like curl -v)?
Open DevTools → Network tab → click the request → Headers section. You can see all request and response headers exactly as curl -v shows them.
Can I abort a fetch request that is already in flight?
Yes, using AbortController. Call controller.abort() at any time and the fetch Promise will reject with an AbortError. This is useful for cancelling requests when a component unmounts in React, or when the user navigates away.
Use our free tool here → JSON Formatter & Validator
Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.