cURLParse cURL Converter

Example: Multipart File Upload with cURL

cURLParse Guides · Updated 2026-10-18 · All guides
curl -X POST https://files.example.com/upload \
  -H 'Authorization: Bearer ***' \
  -F 'file=@report.pdf;type=application/pdf' \
  -F 'metadata={"tag":"q3"}'
  • -F builds multipart/form-data (binary-safe); do not use -d for files — --data-binary @file works only when the API wants raw bytes, not a form.
  • ;type= sets that part's Content-Type. Omit it and curl guesses by extension, which is fine for PDFs and catastrophic for CSVs.
  • Converted to Node fetch: expect a FormData() + Blob construction, no manual boundary. If a converter emits a hand-built multipart/form-data string with a literal boundary, that's a bug in the converter — ours isn't.

What the server actually receives

Dumping the raw request for the command above against a local echo origin shows why -F exists:

POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=------------------------abcdef123456

--------------------------abcdef123456
Content-Disposition: form-data; name="file"; filename="report.pdf"
Content-Type: application/pdf

%PDF-1.4 ...bytes...
--------------------------abcdef123456
Content-Disposition: form-data; name="metadata"
Content-Type: application/json

{"tag":"q3"}
--------------------------abcdef123456--

Three things the transcript proves:

  • Each part gets its own Content-Disposition and Content-Type. The top-level Content-Type only carries the boundary. This is why -F and -d are not interchangeable: -d produces one flat body with one content type.
  • metadata={"tag":"q3"} became a part with Content-Type: application/json because curl recognized the JSON-looking value. That's convenience, not a contract — some APIs expect that part as plain text, and you get to find out in production. Force it: -F 'metadata={"tag":"q3"};type=text/plain'.
  • The boundary is generated per run. Never hardcode one in converted code; a literal boundary in generated output is a converter bug, not a style choice.

The flags around -F that actually bite

  • -F 'file=@big.zip' streams from disk; curl does not buffer the whole file. But -F 'note=<contents.txt' reads the file into the part value — < is read-style, @ is file-style, and both are silent. A missing file is exit 26, never an empty part.
  • -F 'file=@report.pdf;filename=annual.pdf' renames the part. APIs that validate by filename pattern care; your converted code must set the same filename explicitly.
  • -F 'file=@data.csv;type=text/csv' — for CSV the extension guess is text/csv on modern curl but was application/octet-stream on older builds. If an API rejects octet-stream, pin the type.
  • -F 'file=@x.bin;encoding=base64' sets Content-Transfer-Encoding: base64 — a header most modern APIs ignore. Don't reach for it unless the API documents it.
  • No --fail-with-body on an upload means a 413 "file too large" from the edge prints nothing and exits 0. The flags guide covers the assertion shape; uploads are where it matters most because the failure is silent after you paid the egress.

When -F is the wrong flag

--data-binary @report.pdf with -H 'Content-Type: application/pdf' is correct for APIs that take the raw object body (S3 PUT object, some image APIs). The tell: the API docs say "request body is the file", not "multipart form". Sending multipart to a raw-bytes endpoint gives you a body full of boundary lines the server hashes as part of your file — the checksum never matches and no error mentions why.

API saysflagbody on the wire
"multipart/form-data", form field file-F 'file=@x'boundary-delimited parts
"request body is the file"--data-binary @xraw bytes, one content type
"form-urlencoded fields"-d / --data-urlencodek=v&k2=v2

FAQ

Why is my upload corrupted when sent as raw bytes? -d @file treats content as a form-urlencoded string and will mangle binary. -F (or --form) is the multipart, binary-safe path.

Can I upload without reading the whole file into memory? curl streams from disk either way. In Node, pass a Blob backed by fs.openAsBlob() (or a File) to FormData for the same effect.

What does the converted Go code do for -F? It builds a mime.NewWriter multipart writer — see the curl-to-go route for the generated shape.

Developer Sponsor / Partner
Copied to clipboard!