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

1

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.

ItemcURL (Terminal)Fetch API (JavaScript)
HTTP Method-X POSTmethod: "POST"
Headers-H "Key: Value"headers: { Key: "Value" }
Request Body-d '{"key":"val"}'body: JSON.stringify(data)
Basic Auth-u user:passheaders: { Authorization: "Basic ..." }
Bearer Token-H "Authorization: Bearer TOKEN"headers: { Authorization: "Bearer TOKEN" }
Follow Redirects-Lredirect: "follow" (default)
Ignore SSL-k / --insecureN/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+.

2

The Basic GET Request Conversion

The simplest cURL command is a plain GET request. Here is how it maps to fetch:

cURL GET

❌ Bad
curl https://api.example.com/users

Fetch GET

✅ Good
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:

jsget-request.js
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?

Unlike cURL, which exits with a non-zero code on HTTP errors, the Fetch API does NOT throw for HTTP error status codes (4xx, 5xx). You must check response.ok or response.status manually.
3

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

❌ Bad
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice", "email": "alice@example.com"}'

Fetch POST with JSON

✅ Good
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

The 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.
jspost-json.js
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));
4

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

❌ Bad
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

✅ Good
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.

5

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:

1

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...',
  },
});
2

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}`,
  },
});
3

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',
  },
});
4

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());
6

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).

jsform-data.js
// 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

When using 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.
7

Complete Conversion Reference

Here is a comprehensive mapping of every common cURL flag to its fetch equivalent:

ItemcURL FlagFetch Equivalent
URLcurl [URL]fetch('[URL]')
GET method (default)curl [URL]fetch(url) // method defaults to GET
POST method-X POSTmethod: 'POST'
PUT method-X PUTmethod: 'PUT'
PATCH method-X PATCHmethod: 'PATCH'
DELETE method-X DELETEmethod: 'DELETE'
Add header-H "Key: Value"headers: { 'Key': 'Value' }
JSON body-d '{"key":"val"}'body: JSON.stringify(data)
Basic auth-u user:passheaders: { Authorization: 'Basic ' + btoa('user:pass') }
Follow redirects-Lredirect: 'follow' (default)
No redirects--max-redirs 0redirect: 'manual'
Send cookies--cookie "name=val"credentials: 'include'
Verbose output-vN/A (use DevTools Network tab)
Timeout--max-time 10Use AbortController + setTimeout
Compressed--compressedBrowser handles automatically
8

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:

jsfetch-timeout.js
// 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
);
9

CORS Considerations (Not in cURL)

cURL bypasses CORS — fetch does not

cURL runs in your terminal and is not subject to browser CORS policies. When you convert to fetch and run it in a browser, you may hit CORS errors that never appeared with cURL. This is expected behavior — the server must send the appropriate CORS headers for browser requests.

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.

jscors-credentials.js
// 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)
10

A Complete Real-World Example

Here is a complex, real-world cURL command converted to a clean, production-ready fetch function:

bashcomplex-curl.sh
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"
jsstripe-fetch.js
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

Frequently Asked Questions