Developer

CURL TO FETCH

Paste a cURL command and get clean JavaScript fetch() or axios code. Handles headers, auth, JSON body, cookies, and all common flags.

// Paste a cURL command above to convert it.
Supports: -X method · -H headers · -d / --data / --data-raw body · -u user:pass basic auth · -b cookie · --form / -F FormData · multiline with \
Developer Reference

Core Algorithm & Standalone Script

Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.

let currentTab = 'fetch';
  let lastParsed = null;

  // ---------- tokenizer ----------
  function tokenize(str) {
    const tokens = [];
    let i = 0;
    while (i < str.length) {
      // skip whitespace
      while (i < str.length && /\s/.test(str[i])) i++;
      if (i >= str.length) break;
      let tok = '';
      if (str[i] === "'") {
        i++;
        while (i < str.length && str[i] !== "'") { tok += str[i++]; }
        i++; // closing '
      } else if (str[i] === '"') {
        i++;
        while (i < str.length && str[i] !== '"') {
          if (str[i] === '\\' && i + 1 < str.length) { i++; tok += str[i++]; }
          else tok += str[i++];
        }
        i++; // closing "
      } else {
        while (i < str.length && !/\s/.test(str[i])) {
          if (str[i] === '\\' && i + 1 < str.length && str[i+1] === '\n') { i += 2; break; }
          tok += str[i++];
        }
      }
      if (tok) tokens.push(tok);
    }
    return tokens;
  }

  function parseCurl(raw) {
    // normalize line continuations
    const normalized = raw.replace(/\\\s*\n\s*/g, ' ').trim();
    const tokens = tokenize(normalized);
    if (!tokens.length || tokens[0].toLowerCase() !== 'curl') throw new Error('Input must start with "curl"');

    const result = {
      url: '',
      method: 'GET',
      headers: {},
      body: null,
      bodyType: null, // 'raw' | 'form' | 'formdata'
      formFields: [],
      auth: null,
    };

    let i = 1;
    const eat = () => tokens[i++];

    while (i < tokens.length) {
      const tok = eat();
      if (!tok) break;

      if (tok === '-X' || tok === '--request') {
        result.method = eat().toUpperCase();
      } else if (tok.startsWith('-X')) {
        result.method = tok.slice(2).toUpperCase();
      } else if (tok === '-H' || tok === '--header') {
        const hdr = eat();
        const colon = hdr.indexOf(':');
        if (colon > -1) {
          const key = hdr.slice(0, colon).trim();
          const val = hdr.slice(colon + 1).trim();
          result.headers[key] = val;
        }
      } else if (tok.startsWith('--header=')) {
        const hdr = tok.slice(9);
        const colon = hdr.indexOf(':');
        if (colon > -1) result.headers[hdr.slice(0,colon).trim()] = hdr.slice(colon+1).trim();
      } else if (tok === '-d' || tok === '--data' || tok === '--data-raw' || tok === '--data-ascii' || tok === '--data-binary') {
        result.body = eat();
        result.bodyType = 'raw';
        if (!result.method || result.method === 'GET') result.method = 'POST';
      } else if (tok.startsWith('--data=') || tok.startsWith('--data-raw=')) {
        result.body = tok.includes('=') ? tok.slice(tok.indexOf('=')+1) : eat();
        result.bodyType = 'raw';
        if (result.method === 'GET') result.method = 'POST';
      } else if (tok === '-F' || tok === '--form') {
        const field = eat();
        result.formFields.push(field);
        result.bodyType = 'formdata';
        if (result.method === 'GET') result.method = 'POST';
      } else if (tok === '--form-string') {
        const field = eat();
        result.formFields.push(field);
        result.bodyType = 'formdata';
        if (result.method === 'GET') result.method = 'POST';
      } else if (tok === '-u' || tok === '--user') {
        result.auth = eat();
      } else if (tok.startsWith('--user=')) {
        result.auth = tok.slice(7);
      } else if (tok === '-b' || tok === '--cookie') {
        result.headers['Cookie'] = eat();
      } else if (tok === '--cookie-jar' || tok === '-c') {
        eat(); // skip jar file arg
      } else if (tok === '-o' || tok === '--output') {
        eat(); // skip output file
      } else if (tok === '--url') {
        result.url = eat();
      } else if (tok === '-L' || tok === '--location' || tok === '--compressed' ||
                 tok === '-s' || tok === '--silent' || tok === '-k' || tok === '--insecure' ||
                 tok === '-v' || tok === '--verbose' || tok === '-i' || tok === '--include' ||
                 tok === '--no-keepalive' || tok === '--http1.1' || tok === '--http2') {
        // ignore
      } else if (tok === '--connect-timeout' || tok === '--max-time' || tok === '-m') {
        eat(); // skip value
      } else if (tok === '--proxy' || tok === '-x') {
        eat();
      } else if (!tok.startsWith('-')) {
        if (!result.url) result.url = tok;
      }
    }

    if (!result.url) throw new Error('No URL found in cURL command');

    // Basic auth → Authorization header
    if (result.auth) {
      result.headers['Authorization'] = 'Basic ' + btoa(result.auth);
    }

    // If body looks like JSON, ensure Content-Type
    if (result.body && result.bodyType === 'raw' && !result.headers['Content-Type']) {
      try { JSON.parse(result.body); result.headers['Content-Type'] = 'application/json'; } catch(e) {}
    }

    return result;
  }

  function indent(str, spaces) {
    return str.split('\n').map(l => ' '.repeat(spaces) + l).join('\n');
  }

  function renderFetch(p) {
    const hasOptions = p.method !== 'GET' || Object.keys(p.headers).length || p.body || p.formFields.length;
    if (!hasOptions) return `const response = await fetch('${p.url}');\nconst data = await response.json();`;

    let lines = [`const response = await fetch('${p.url}', {`];
    if (p.method !== 'GET') lines.push(`  method: '${p.method}',`);

    const hkeys = Object.keys(p.headers);
    if (hkeys.length) {
      lines.push('  headers: {');
      hkeys.forEach(k => lines.push(`    '${k}': '${p.headers[k]}',`));
      lines.push('  },');
    }

    if (p.bodyType === 'raw' && p.body) {
      // detect JSON
      try {
        JSON.parse(p.body);
        lines.push(`  body: JSON.stringify(${p.body}),`);
      } catch(e) {
        lines.push(`  body: '${p.body.replace(/'/g, "\\'")}',`);
      }
    } else if (p.bodyType === 'formdata' && p.formFields.length) {
      lines.push('  body: (() => {');
      lines.push('    const fd = new FormData();');
      p.formFields.forEach(f => {
        const eq = f.indexOf('=');
        if (eq > -1) {
          const k = f.slice(0, eq);
          const v = f.slice(eq + 1);
          lines.push(`    fd.append('${k}', '${v}');`);
        }
      });
      lines.push('    return fd;');
      lines.push('  })(),');
    }

    lines.push('});');
    lines.push('\nconst data = await response.json();');
    return lines.join('\n');
  }

  function renderAxios(p) {
    const config = {};
    if (Object.keys(p.headers).length) config.headers = p.headers;

    if (p.bodyType === 'raw' && p.body) {
      try { config.data = JSON.parse(p.body); } catch(e) { config.data = p.body; }
    } else if (p.bodyType === 'formdata' && p.formFields.length) {
      // FormData note
    }

    const method = p.method.toLowerCase();
    const urlStr = `'${p.url}'`;

    const hasCfg = Object.keys(config).length > 0;
    let cfgStr = '';
    if (hasCfg) {
      cfgStr = JSON.stringify(config, null, 2).replace(/"([^"]+)":/g, '$1:');
    }

    let call;
    if (p.bodyType === 'formdata' && p.formFields.length) {
      let lines = ['const fd = new FormData();'];
      p.formFields.forEach(f => {
        const eq = f.indexOf('=');
        if (eq > -1) lines.push(`fd.append('${f.slice(0,eq)}', '${f.slice(eq+1)}');`);
      });
      lines.push('');
      const hdrStr = Object.keys(p.headers).length ? `, { headers: ${JSON.stringify(p.headers, null, 2)} }` : '';
      lines.push(`const { data } = await axios.${method}(${urlStr}, fd${hdrStr});`);
      return lines.join('\n');
    }

    if (method === 'get' || method === 'delete' || method === 'head' || method === 'options') {
      call = hasCfg ? `const { data } = await axios.${method}(${urlStr}, ${cfgStr});` : `const { data } = await axios.${method}(${urlStr});`;
    } else {
      const dataVal = config.data !== undefined ? JSON.stringify(config.data, null, 2) : 'null';
      const restCfg = { ...config }; delete restCfg.data;
      const restStr = Object.keys(restCfg).length ? `, ${JSON.stringify(restCfg, null, 2).replace(/"([^"]+)":/g, '$1:')}` : '';
      call = `const { data } = await axios.${method}(${urlStr}, ${dataVal}${restStr});`;
    }
    return call;
  }

  function renderNodeFetch(p) {
    let lines = ["const fetch = require('node-fetch');", ''];
    const fetchCode = renderFetch(p);
    lines.push('(async () => {');
    fetchCode.split('\n').forEach(l => lines.push('  ' + l));
    lines.push('  console.log(data);');
    lines.push('})();');
    return lines.join('\n');
  }

  function setOutput(code) {
    document.getElementById('outputCode').textContent = code;
  }

  function convert() {
    const raw = document.getElementById('curlInput').value.trim();
    const errBar = document.getElementById('errorBar');
    if (!raw) { setOutput('// Paste a cURL command above to convert it.'); errBar.style.display = 'none'; return; }
    try {
      lastParsed = parseCurl(raw);
      errBar.style.display = 'none';
      renderTab();
    } catch(e) {
      errBar.textContent = '⚠ ' + e.message;
      errBar.style.display = 'block';
      setOutput('// Fix the error above and try again.');
      lastParsed = null;
    }
  }

  function renderTab() {
    if (!lastParsed) return;
    if (currentTab === 'fetch') setOutput(renderFetch(lastParsed));
    else if (currentTab === 'axios') setOutput(renderAxios(lastParsed));
    else setOutput(renderNodeFetch(lastParsed));
  }

  function switchTab(name) {
    currentTab = name;
    document.querySelectorAll('.tab').forEach((t, i) => {
      t.classList.toggle('active', ['fetch','axios','node'][i] === name);
    });
    renderTab();
  }

  function copyOutput() {
    const code = document.getElementById('outputCode').textContent;
    if (code.startsWith('//')) return;
    navigator.clipboard.writeText(code).catch(() => {});
    const btn = document.getElementById('copyBtn');
    btn.textContent = 'copied!'; btn.classList.add('copied');
    setTimeout(() => { btn.textContent = 'copy'; btn.classList.remove('copied'); }, 2000);
  }