How to Convert cURL Command to JavaScript Fetch: Complete Guide (2026)
Converting cURL commands to JavaScript fetch is one of the most common tasks developers face when integrating APIs. Whether you copied a command from API docs or need to move CLI testing into your app, this guide covers every flag, every edge case, and every pattern you need — with real code examples throughout.
95%
of API docs include cURL examples
8
common cURL flags to know
1:1
mapping between cURL and fetch options
100%
of fetch options have cURL equivalents
Understanding cURL vs. JavaScript Fetch
cURL is a command-line tool that sends HTTP requests directly from your terminal. The Fetch API is JavaScript's built-in mechanism for making HTTP requests from the browser or Node.js. Despite looking completely different, both tools do the same thing: they send HTTP messages over a network.
| Item | cURL (Terminal) | Fetch API (JavaScript) |
|---|---|---|
| HTTP Method | -X POST | method: "POST" |
| Headers | -H "Key: Value" | headers: { Key: "Value" } |
| Request Body | -d '{"key":"val"}' | body: JSON.stringify(data) |
| Basic Auth | -u user:pass | headers: { Authorization: "Basic ..." } |
| Bearer Token | -H "Authorization: Bearer TOKEN" | headers: { Authorization: "Bearer TOKEN" } |
| Follow Redirects | -L | redirect: "follow" (default) |
| Ignore SSL | -k / --insecure | N/A (browser enforces SSL) |
| Form Data | -F "file=@path" | body: new FormData() |
Quick fact
The Fetch API was introduced in browsers around 2015. Before that, developers used XMLHttpRequest (XHR). Today, fetch is available natively in all modern browsers and Node.js 18+.
The Basic GET Request Conversion
The simplest cURL command is a plain GET request. Here is how it maps to fetch:
cURL GET
curl https://api.example.com/usersFetch GET
fetch('https://api.example.com/users')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));With async/await, which is the preferred modern pattern:
async function getUsers() {
try {
const response = await fetch('https://api.example.com/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error('Fetch failed:', error);
}
}
getUsers();Why check response.ok?
response.ok or response.status manually.Converting POST Requests with JSON Body
POST requests with JSON bodies are the most common API pattern. The cURL flags -X POST, -H, and -d all have direct fetch equivalents.
cURL POST with JSON
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'Fetch POST with JSON
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Alice',
email: 'alice@example.com',
}),
});
const data = await response.json();Always stringify the body
body field in fetch must be a string, Blob, ArrayBuffer, FormData, or URLSearchParams — NOT a plain object. Passing a raw object silently converts to [object Object]. Always use JSON.stringify() for JSON payloads.async function createUser(userData) {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(userData),
});
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`Request failed ${response.status}: ${errorBody}`);
}
return response.json();
}
// Usage
createUser({ name: 'Alice', email: 'alice@example.com' })
.then(user => console.log('Created:', user))
.catch(err => console.error(err));Converting Headers (-H flag)
Every -H "Key: Value" flag in cURL becomes a key-value pair in the headers object. Multiple -H flags become multiple entries in the same object.
Multiple cURL -H flags
curl https://api.example.com/data \
-H "Authorization: Bearer eyJhbGci..." \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "X-Request-ID: abc123" \
-H "Cache-Control: no-cache"Fetch headers object
const response = await fetch('https://api.example.com/data', {
headers: {
'Authorization': 'Bearer eyJhbGci...',
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Request-ID': 'abc123',
'Cache-Control': 'no-cache',
},
});Header names are case-insensitive
HTTP headers are case-insensitive. "content-type" and "Content-Type" are the same header, but conventionally use Title-Case.
Content-Type is critical
If you send a JSON body, you must include Content-Type: application/json or many APIs will reject your request.
Authorization format matters
Bearer tokens use "Bearer TOKEN", Basic auth uses "Basic base64(user:pass)". Match the API's expected format exactly.
Custom headers with X- prefix
Many APIs use custom X-* headers for things like API versions, request IDs, or tenant identifiers. These map directly.
Converting Authentication (-u and Bearer tokens)
Authentication is one of the trickier conversions because cURL has dedicated flags for some auth types. Here are the three most common patterns:
Bearer Token (OAuth / JWT)
The most common modern auth pattern. cURL uses -H "Authorization: Bearer TOKEN" which maps directly to the headers object.
// cURL: curl -H "Authorization: Bearer eyJhbGci..." https://api.example.com/me
const response = await fetch('https://api.example.com/me', {
headers: {
'Authorization': 'Bearer eyJhbGci...',
},
});Basic Auth (-u username:password)
cURL's -u flag encodes credentials as Base64. In fetch, you must perform this encoding manually using btoa().
// cURL: curl -u alice:secret123 https://api.example.com/admin
const credentials = btoa('alice:secret123'); // Base64 encode
const response = await fetch('https://api.example.com/admin', {
headers: {
'Authorization': `Basic ${credentials}`,
},
});API Key Header
Many APIs use a custom header for API keys. This maps directly to the headers object.
// cURL: curl -H "X-API-Key: my-secret-key" https://api.example.com/data
const response = await fetch('https://api.example.com/data', {
headers: {
'X-API-Key': 'my-secret-key',
},
});API Key as Query Parameter
Some APIs accept the key in the URL query string instead of headers.
// cURL: curl "https://api.example.com/data?api_key=my-secret"
const response = await fetch('https://api.example.com/data?api_key=my-secret');
// Or using URL constructor for cleaner code:
const url = new URL('https://api.example.com/data');
url.searchParams.set('api_key', 'my-secret');
const response2 = await fetch(url.toString());Converting Form Data (-F and --data-urlencode)
Form submissions require different handling depending on whether they use URL-encoded form data or multipart form data (for file uploads).
// URL-encoded form data
// cURL: curl -d "username=alice&password=secret" https://api.example.com/login
const params = new URLSearchParams();
params.append('username', 'alice');
params.append('password', 'secret');
const response = await fetch('https://api.example.com/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString(),
});
// --- OR for multipart form data (file uploads) ---
// cURL: curl -F "avatar=@/path/to/photo.jpg" -F "userId=123" https://api.example.com/upload
const formData = new FormData();
formData.append('avatar', fileInput.files[0]); // File object from <input type="file">
formData.append('userId', '123');
// NOTE: Do NOT set Content-Type header when using FormData.
// The browser sets it automatically with the correct boundary.
const uploadResponse = await fetch('https://api.example.com/upload', {
method: 'POST',
body: formData,
});Never set Content-Type manually for FormData
new FormData(), do NOT manually set the Content-Type header. The browser automatically sets it to multipart/form-data with the correct boundary string. Setting it manually will break the request.Complete Conversion Reference
Here is a comprehensive mapping of every common cURL flag to its fetch equivalent:
| Item | cURL Flag | Fetch Equivalent |
|---|---|---|
| URL | curl [URL] | fetch('[URL]') |
| GET method (default) | curl [URL] | fetch(url) // method defaults to GET |
| POST method | -X POST | method: 'POST' |
| PUT method | -X PUT | method: 'PUT' |
| PATCH method | -X PATCH | method: 'PATCH' |
| DELETE method | -X DELETE | method: 'DELETE' |
| Add header | -H "Key: Value" | headers: { 'Key': 'Value' } |
| JSON body | -d '{"key":"val"}' | body: JSON.stringify(data) |
| Basic auth | -u user:pass | headers: { Authorization: 'Basic ' + btoa('user:pass') } |
| Follow redirects | -L | redirect: 'follow' (default) |
| No redirects | --max-redirs 0 | redirect: 'manual' |
| Send cookies | --cookie "name=val" | credentials: 'include' |
| Verbose output | -v | N/A (use DevTools Network tab) |
| Timeout | --max-time 10 | Use AbortController + setTimeout |
| Compressed | --compressed | Browser handles automatically |
Handling Timeouts with AbortController
cURL has a --max-time flag. Fetch does not have a native timeout option, but you can implement one using AbortController:
// cURL: curl --max-time 10 https://api.example.com/slow-endpoint
async function fetchWithTimeout(url, options = {}, timeoutMs = 10000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
});
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error(`Request timed out after ${timeoutMs}ms`);
}
throw error;
}
}
// Usage
const response = await fetchWithTimeout(
'https://api.example.com/slow-endpoint',
{ headers: { 'Authorization': 'Bearer TOKEN' } },
10000 // 10 seconds
);CORS Considerations (Not in cURL)
cURL bypasses CORS — fetch does not
cURL has no CORS
cURL makes direct TCP connections. CORS is purely a browser security mechanism. A working cURL command can still fail in the browser due to missing CORS headers.
Add credentials for cookies
If the API uses cookies for auth, add credentials: "include" to fetch. This is not needed with cURL because it handles cookies differently.
Same-origin requests
Fetch requests to the same origin (same domain, port, protocol) have no CORS restrictions. Only cross-origin requests are affected.
Preflight requests
For non-simple requests (custom headers, JSON content-type), browsers send an OPTIONS preflight. cURL never does this automatically.
// Sending cookies/credentials cross-origin
// cURL: curl --cookie "session=abc" https://api.otherdomain.com/data
const response = await fetch('https://api.otherdomain.com/data', {
credentials: 'include', // Send cookies with cross-origin requests
});
// Options for credentials:
// 'omit' - never send cookies (default for cross-origin)
// 'same-origin' - send cookies only for same-origin requests (default)
// 'include' - always send cookies (requires server CORS: Access-Control-Allow-Credentials: true)A Complete Real-World Example
Here is a complex, real-world cURL command converted to a clean, production-ready fetch function:
curl -X POST https://api.stripe.com/v1/charges \
-H "Authorization: Bearer sk_test_abc123" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Stripe-Version: 2023-10-16" \
-d "amount=2000" \
-d "currency=usd" \
-d "source=tok_visa" \
-d "description=Charge for alice@example.com"async function createCharge({ amount, currency, source, description }) {
const params = new URLSearchParams({
amount: String(amount),
currency,
source,
description,
});
const response = await fetch('https://api.stripe.com/v1/charges', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_test_abc123',
'Content-Type': 'application/x-www-form-urlencoded',
'Stripe-Version': '2023-10-16',
},
body: params.toString(),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Stripe error: ${error.error?.message}`);
}
return response.json();
}
// Usage
const charge = await createCharge({
amount: 2000,
currency: 'usd',
source: 'tok_visa',
description: 'Charge for alice@example.com',
});
console.log('Charge created:', charge.id);Parse cURL
Extract URL
Map Method
Map Headers
Map Body
Map Auth
Test Fetch