cURL Builder: From Visual Interface to Command Line Generation#

Often need to send curl commands to backend colleagues for API debugging. Writing them by hand means dealing with quote escaping and parameter concatenation—easy to make mistakes. Built a visual builder to generate complete commands with a few clicks.

Breaking Down cURL Command Structure#

A complete curl command has these parts:

curl -X POST \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer xxx' \
  -d '{"name": "test"}' \
  'https://api.example.com/users?page=1'

Breakdown:

  1. HTTP method: -X POST/PUT/DELETE, GET can be omitted
  2. Headers: -H 'Key: Value', multiple allowed
  3. Body: -d 'data' or -F 'field=value' (form)
  4. URL: Last parameter, with query params

Builder’s core is assembling these parts into valid command strings.

Quote Escaping: The Headache#

User input may contain single quotes, like password it's secret. Direct concatenation breaks command structure.

Solution: Escape single quotes with '\'' inside single-quoted strings:

function escapeSingleQuote(s: string): string {
  return s.replace(/'/g, "'\\''")
}

How it works:

# Original: it's secret
# Escaped: 'it'\''s secret'
# Parse: 'it' + \' + 's secret'

Three parts concatenated: end previous quoted region, escaped quote, start new quoted region.

Handling URL Query Parameters#

User might input:

  • Base URL: https://api.example.com/users
  • Query params: page=1, size=20

Need to assemble: https://api.example.com/users?page=1&size=20

Implementation:

function buildQueryString(params: KeyValue[]): string {
  const active = params.filter(p => p.key.trim() !== '')
  if (active.length === 0) return ''
  return '?' + active
    .map(p => `${encodeURIComponent(p.key)}=${encodeURIComponent(p.value)}`)
    .join('&')
}

Note encodeURIComponent: param values may contain & or =, need encoding.

Three Body Modes#

1. Raw Mode#

Pass raw string, usually JSON:

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

Frontend uses textarea, add “Format JSON” button:

const handleFormatJson = () => {
  try {
    const parsed = JSON.parse(rawBody)
    setRawBody(JSON.stringify(parsed, null, 2))
  } catch {
    // Ignore invalid JSON
  }
}

2. form-data Mode#

Multipart form, use -F parameter:

curl -X POST \
  -F 'username=admin' \
  -F 'password=secret' \
  https://api.example.com/login

Each field gets its own -F, supports file upload (-F 'file=@path').

3. x-www-form-urlencoded Mode#

URL-encoded form, use -d parameter:

curl -X POST \
  -d 'username=admin&password=secret' \
  https://api.example.com/login

All fields concatenated into one string, & separated, keys and values URL-encoded.

Generating Auth Headers#

Basic Auth#

Base64 encode username:password:

if (authType === 'basic' && authUsername) {
  const cred = btoa(`${authUsername}:${authPassword}`)
  parts.push(`-H 'Authorization: Basic ${cred}'`)
}

btoa is browser’s built-in Base64 encoding function.

Bearer Token#

Direct concatenation:

if (authType === 'bearer' && authToken) {
  parts.push(`-H 'Authorization: Bearer ${authToken}'`)
}

Common Option Mapping#

cURL has many options, picking the useful ones:

Option Meaning Use Case
-i Include response headers View status code and headers
-v Verbose output Debug connection issues
-L Follow redirects Short link redirects
-k Ignore SSL cert Self-signed cert testing
-s Silent mode Only get response body
--compressed Request compression Large response bodies
-o file Output to file Download files
--max-time N Timeout seconds Prevent hanging

Frontend uses checkbox controls:

if (opts.includeHeaders) parts.push('-i')
if (opts.verbose) parts.push('-v')
if (opts.followRedirects) parts.push('-L')
if (opts.insecureSsl) parts.push('-k')

URL Quote Handling#

URL may contain special characters (&, space, *), need to decide whether to quote:

const needsQuoting = /[&\s'"()[\]{}*$|^<>`\\]/.test(fullUrl)
const urlStr = needsQuoting
  ? `'${escapeSingleQuote(fullUrl)}'`
  : fullUrl
parts.push(urlStr)

Regex check: if contains these special characters, wrap in single quotes.

Quick Add Common Headers#

Preset common headers for one-click addition:

const COMMON_HEADERS = {
  contentType: { key: 'Content-Type', value: 'application/json' },
  authorization: { key: 'Authorization', value: 'Bearer ' },
  accept: { key: 'Accept', value: 'application/json' },
  userAgent: { key: 'User-Agent', value: 'curl/8.0' },
  cookie: { key: 'Cookie', value: '' },
  referer: { key: 'Referer', value: '' },
}

const addCommonHeader = (name: string) => {
  const h = COMMON_HEADERS[name]
  if (h) {
    setHeaders(prev => [...prev, { id: nextId(), key: h.key, value: h.value }])
  }
}

Dropdown selection auto-fills key-value.

State Management Structure#

Entire form in one state object:

const [url, setUrl] = useState('')
const [method, setMethod] = useState('GET')
const [headers, setHeaders] = useState<KeyValue[]>([])
const [queryParams, setQueryParams] = useState<KeyValue[]>([])
const [bodyMode, setBodyMode] = useState<'raw' | 'form-data' | 'x-www-form-urlencoded'>('raw')
const [rawBody, setRawBody] = useState('')
const [formFields, setFormFields] = useState<KeyValue[]>([])
const [authType, setAuthType] = useState<'none' | 'basic' | 'bearer'>('none')
// ... option states

KeyValue interface for dynamic lists:

interface KeyValue {
  id: number
  key: string
  value: string
}

Add/remove/update operations:

const addHeader = () => {
  setHeaders(prev => [...prev, { id: nextId(), key: '', value: '' }])
}

const removeHeader = (id: number) => {
  setHeaders(prev => prev.filter(h => h.id !== id))
}

const updateHeader = (id: number, field: 'key' | 'value', val: string) => {
  setHeaders(prev => prev.map(h => h.id === id ? { ...h, [field]: val } : h))
}

Command Generation Function#

Core is assembling all parts:

function buildCurlCommand(opts: Options): string {
  const parts: string[] = ['curl']

  // Method
  if (opts.method !== 'GET') {
    parts.push(`-X ${opts.method}`)
  }

  // Options
  if (opts.includeHeaders) parts.push('-i')
  if (opts.verbose) parts.push('-v')
  // ...

  // Headers
  for (const h of opts.headers) {
    if (h.key.trim()) {
      parts.push(`-H '${escapeSingleQuote(h.key)}: ${escapeSingleQuote(h.value)}'`)
    }
  }

  // Auth
  if (opts.authType === 'basic' && opts.authUsername) {
    const cred = btoa(`${opts.authUsername}:${opts.authPassword}`)
    parts.push(`-H 'Authorization: Basic ${cred}'`)
  }

  // URL
  const fullUrl = opts.url + buildQueryString(opts.queryParams)
  parts.push(quoteUrl(fullUrl))

  // Body
  if (['POST', 'PUT', 'PATCH'].includes(opts.method)) {
    if (opts.bodyMode === 'raw' && opts.rawBody.trim()) {
      parts.push(`-d '${escapeSingleQuote(opts.rawBody)}'`)
    } else if (opts.bodyMode === 'form-data') {
      for (const f of opts.formFields) {
        parts.push(`-F '${escapeSingleQuote(f.key)}=${escapeSingleQuote(f.value)}'`)
      }
    }
    // ...
  }

  return parts.join(' ')
}

Cache with useMemo, depend on all states:

const curlCommand = useMemo(() => buildCurlCommand({
  url, method, headers, queryParams, bodyMode, rawBody, formFields,
  authType, authUsername, authPassword, authToken,
  includeHeaders, verbose, followRedirects, insecureSsl, silent,
  outputFile, compressed, maxTime,
}), [url, method, headers, /* ... */])

Final Result#

Built an online tool: cURL Builder

Features:

  • 7 HTTP methods
  • URL + query param assembly
  • Three body modes
  • Basic/Bearer auth
  • Common options one-click toggle
  • One-click copy command

Writing curl by hand is error-prone. Visual builder guarantees generated commands are always valid.


Related tools: cURL Converter | API Tester