cURL to Code Converter

Paste a curl command and get the same request as fetch, axios, Python requests, PHP cURL or Go. The usual source is DevTools: right-click a request in the Network tab, choose Copy as cURL, and paste it here. Flags the tool cannot translate are listed in a note under the output, so you can see what was left out.

cURL Command
JavaScript (fetch)

1 cURL to Code Converter example

curl

curl 'https://api.example.com/v1/orders' \
  -H 'content-type: application/json' \
  -u 'shop:s3cret' \
  --data-raw '{"sku":"TSHIRT-M","qty":2}'

Python (requests)

import requests

headers = {
    "content-type": "application/json",
}

json_data = {
    "sku": "TSHIRT-M",
    "qty": 2,
}

response = requests.post(
    "https://api.example.com/v1/orders",
    headers=headers,
    auth=("shop", "s3cret"),
    json=json_data,
)

print(response.status_code)
print(response.text)

The JSON body became a dict passed as json=, and -u became auth= instead of a hand-built Authorization header. Each target uses its library's own option where one exists.

2 What gets translated

Quoting, the way bash does it

Single quotes, double quotes, backslash line continuations and the $'...' strings Chrome uses when a body contains an apostrophe. The Windows cmd version of Copy as cURL is read on a best effort basis, so pick the bash version when you can.

Bodies

-d, --data-raw, --data-binary and --data-urlencode (repeated ones joined with &), --json, and -F multipart fields including @file uploads. With -G the data moves into the query string, as curl does.

Headers and auth

-H, -A, -e, -b cookies and -u basic auth. A -d without a Content-Type gets application/x-www-form-urlencoded written out explicitly, because curl adds that header and most clients do not.

Switches

-X, -I, -L, -k, --compressed and -m map to each client's own option. Output-only flags like -s, -v and -o are dropped without a note because they do not change the request. Any other unknown flag is named in the note under the code.

3 Browser fetch cannot send every header

A request copied from DevTools carries headers the browser sets itself: Cookie, Origin, Referer and the whole sec-ch-ua and sec-fetch family. Page scripts are not allowed to set those, and fetch drops them without an error. Pick Node.js (fetch) when the code runs on a server, where those limits do not apply. The JavaScript (fetch) target keeps them as comments so you can see what the original request sent, and adds credentials: "include" when there were cookies.

Redirects differ too. curl stops at a 3xx unless you pass -L, while fetch, requests and Go follow redirects by default. PHP cURL behaves like the command line, so only that target gets CURLOPT_FOLLOWLOCATION, and only when the command had -L. If the command carries a live session cookie or token, the generated code contains it too, so keep it out of commits and shared snippets.

How to use the cURL to Code Converter tool

  1. In Chrome or Firefox DevTools, open the Network tab, right-click the request and choose Copy as cURL (use the bash version on Windows). Or write the command yourself.
  2. Paste it into the cURL Command panel on the left. Multi-line commands with backslash continuations are fine.
  3. Pick the language from the Target dropdown: JavaScript (fetch), JavaScript (axios), Node.js (fetch), Python (requests), PHP (cURL) or Go (net/http). The choice is remembered for next time.
  4. Read the notes under the code. Flags that were skipped, files that the code expects on disk, and headers a browser will refuse all show up there.
  5. Click Copy Code, or Clear to start over. Dummy Data loads a POST with a JSON body, a bearer token and cookies if you want to see every target's output first.

How to parse a curl command in Python and JavaScript

Python

import shlex

cmd = """curl 'https://httpbin.org/post' \\
  -H 'content-type: application/json' \\
  --data-raw '{"sku":"TSHIRT-M","qty":2}'"""

# shlex does not know about line continuations, so drop them first
args = shlex.split(cmd.replace("\\\n", " "))
assert args.pop(0) == "curl"

method, url, headers, data = None, None, {}, []
it = iter(args)
for arg in it:
    if arg in ("-X", "--request"):
        method = next(it)
    elif arg in ("-H", "--header"):
        name, _, value = next(it).partition(":")
        headers[name.strip()] = value.strip()
    elif arg in ("-d", "--data", "--data-raw", "--data-binary"):
        data.append(next(it))
    elif not arg.startswith("-"):
        url = arg

body = "&".join(data) or None
method = method or ("POST" if body else "GET")
print(method, url, headers, body)

# then, with requests installed:
# response = requests.request(method, url, headers=headers, data=body)

shlex.split follows POSIX quoting but does not handle backslash line continuations or bash's $'...' strings. Without the replace(), each continuation turns into a stray newline argument.

JavaScript

// Split a shell command into words: quotes, backslashes and line continuations
function shellWords(cmd) {
  const words = [];
  let cur = "", inWord = false;
  for (let i = 0; i < cmd.length; i++) {
    const c = cmd[i];
    if (c === "\\") {
      if (cmd[i + 1] !== "\n") { cur += cmd[i + 1] ?? ""; inWord = true; }
      i++;
    } else if (c === "'") {
      const end = cmd.indexOf("'", i + 1);
      if (end === -1) throw new Error("Unterminated single quote");
      cur += cmd.slice(i + 1, end); inWord = true; i = end;
    } else if (c === '"') {
      inWord = true;
      for (i++; i < cmd.length && cmd[i] !== '"'; i++) {
        if (cmd[i] === "\\" && '"\\$`\n'.includes(cmd[i + 1])) i++;
        if (cmd[i] !== "\n" || cmd[i - 1] !== "\\") cur += cmd[i];
      }
    } else if (/\s/.test(c)) {
      if (inWord) words.push(cur);
      cur = ""; inWord = false;
    } else { cur += c; inWord = true; }
  }
  if (inWord) words.push(cur);
  return words;
}

const cmd = `curl 'https://httpbin.org/post' \\
  -H 'content-type: application/json' \\
  -H "x-note: say \\"hi\\"" \\
  --data-raw '{"sku":"TSHIRT-M","qty":2}'`;

const [, ...args] = shellWords(cmd);
const init = { method: "GET", headers: {} };
let url;
for (let i = 0; i < args.length; i++) {
  const a = args[i];
  if (a === "-X" || a === "--request") init.method = args[++i];
  else if (a === "-H" || a === "--header") {
    const [name, ...rest] = args[++i].split(":");
    init.headers[name.trim()] = rest.join(":").trim();
  } else if (a === "-d" || a === "--data" || a === "--data-raw") {
    init.body = init.body ? init.body + "&" + args[++i] : args[++i];
    if (init.method === "GET") init.method = "POST";
  } else if (!a.startsWith("-")) url = a;
}

console.log(url, init);
// const response = await fetch(url, init);

This covers commands you wrote yourself. It does not handle $'...' strings, combined short flags like -sSL, or --data-urlencode, and those cases account for most of the extra code in the tool above.

Common errors and how to fix them

Unterminated single quote
The paste got cut off, or the command tries to put an apostrophe inside single quotes, which bash does not allow. Write it as 'it'\''s' or as $'it\'s', the form Chrome uses.
TypeError: Failed to fetch, while the same curl command works
curl ignores CORS and a browser does not. If the server does not send Access-Control-Allow-Origin for your page, the request is blocked before your code sees a response. Make the call from a server (use the Node.js target) or fix the CORS headers.
401 or 403 from the generated code
Copied commands carry the session cookie and tokens from the moment you copied them, and those expire. Browser fetch also ignores the Cookie header entirely. Copy a fresh request, or log in from the code instead.
TypeError: Request with GET/HEAD method cannot have body
The command combined -X GET with -d. curl sends that request, but fetch refuses it. If the data belongs in the query string, use -G instead of -X GET and the tool moves it there.
requests.exceptions.SSLError: CERTIFICATE_VERIFY_FAILED
The server's certificate is self-signed or its chain is incomplete. The command probably had -k, which becomes verify=False. That is fine against localhost and a bad idea anywhere else: point verify= at the CA bundle instead.

Good to know

curl joins repeated -d values with & and sends them as application/x-www-form-urlencoded unless you set another Content-Type. Most HTTP clients send a bare string as text/plain, so the tool writes the Content-Type header out explicitly whenever curl would have added it. --json is shorthand for the same data plus Content-Type and Accept headers set to application/json.

Specifications and references

This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.

Frequently Asked Questions

How do I convert a curl command to Python requests?

Paste the command, pick Python (requests) from the Target dropdown and copy the result. JSON bodies become a dict passed as json=, -u becomes auth=, and -k becomes verify=False.

Does it work with Chrome's Copy as cURL?

Yes, that is the main use case, including the $'...' strings Chrome writes when a body contains an apostrophe. On Windows, DevTools offers a cmd and a bash version, and we recommend bash. The tool reads the cmd version too, but only on a best effort basis.

Why are some headers commented out in the fetch output?

Browsers do not let page scripts set Cookie, Origin, Referer, Host or any sec- header, and fetch drops them without an error. The tool keeps them as comments so you can see what the original request sent. The Node.js (fetch) target keeps them as real headers.

What happens to curl options it does not understand?

They are listed in a note under the generated code. Options that only change curl's own output, like -s, -v, -i and -o, are ignored without a note because they do not change the request.

Is it safe to paste a command that contains my API token?

The command is parsed by JavaScript in this page and is not sent anywhere. The generated code still contains the token, though, so keep it out of commits and rotate the token if the code gets shared.

Working with the response

Tidy the JSON that comes back with the JSON formatter, generate types for it with JSON to TypeScript, pull apart a long URL in the URL parser, or check a bearer token in the JWT decoder.

More Data Converters

Browse all data converters

Copied to clipboard!