Tutorial

cURL POST request: form data, JSON and file uploads

Send POST requests with curl: -d, --data-urlencode, --json, -F uploads and Windows quoting. Every command run on 10 October 2026, with the server's echo.

HProxy Team··Updated October 10, 2026·9 min read
HProxy.Tutorial

Skip the dead lists.

Our free proxy list re-checks every exit every few minutes across 100+ countries, with a live last-checked time, so you copy IPs that worked moments ago, not a stale text dump.

Open the free proxy list→

To send a POST request with curl, add -d with your data: curl -d 'name=alice' URL sends a form, the way a browser does. For JSON, use --json '{"name":"alice"}', which also sets the JSON content type. To upload a file, use -F 'file=@report.csv'. All three make the request a POST on their own, so you do not need -X POST.

Every command here ran on 10 October 2026 on our Ubuntu 24.04 server, with its curl 8.5.0. The target was httpbin.org, which answers with what it received, so each output shows how the server read the body. We also tested the same JSON from three Windows shells with the curl.exe 8.21.0 that Windows 11 ships.

Which curl option fits which body?

curl has six ways to send a body. They differ in the content type, in what @ means and in what happens to newlines. The manual describes each one:

OptionContent type curl sendsA leading @ meansNewlines from a fileUse it for
-dapplication/x-www-form-urlencodedread a fileremovedSimple form fields.
--data-urlencodeapplication/x-www-form-urlencodedread a filekept, then encodedForm values with spaces or &.
--data-rawapplication/x-www-form-urlencodeda literal @(no file)Text that starts with @.
--data-binaryapplication/x-www-form-urlencodedread a filekeptExact bytes, with your own -H.
--jsonapplication/jsonread a filekeptJSON, from curl 7.82.0.
-Fmultipart/form-dataattach a filekeptFile uploads and mixed forms.

You need curl in a terminal. What is cURL? shows how to check your version, which matters for --json.

How do you send form fields?

Two terminal captures from 10 October 2026, curl 8.5.0, one above the other. Above: -d name=alice -d tool=curl reaches httpbin as a form with both fields, sent as application/x-www-form-urlencoded. -d 'q=fish & chips' arrives as two broken fields, while --data-urlencode keeps fish & chips whole. -d '@alice' stops with curl: option -d: error encountered when reading a file. --data-raw sends @alice as text. -G -d q=curl turns into a GET with q in the query. Below: --json arrives as JSON with Content-Type and Accept set to application/json. The same JSON sent with -d arrives as one form key. A JSON file sent with -d loses its newlines, and with --data-binary keeps them. -X PUT --json arrives as a PUT.
Our own runs on our server, 10 October 2026, 20:32 and 20:33 UTC: curl 8.5.0 on Ubuntu 24.04, captured off-screen. jq kept only the fields httpbin.org echoed back.

Step 1: send fields with -d

curl -s -d 'name=alice' -d 'tool=curl' https://httpbin.org/post

httpbin read {"name":"alice","tool":"curl"} as form fields, sent as application/x-www-form-urlencoded. curl joins several -d options with &, so this is the same as -d 'name=alice&tool=curl'.

Step 2: encode values with spaces or &

-d sends the text exactly as written. A value with a space and an & breaks into pieces:

$ curl -s -d 'q=fish & chips' https://httpbin.org/post | jq -c .form
{" chips":"","q":"fish "}
$ curl -s --data-urlencode 'q=fish & chips' https://httpbin.org/post | jq -c .form
{"q":"fish & chips"}

The & ended the first field, so the server saw q as fish and a second, empty field. --data-urlencode encodes the part after =, and the value arrived whole.

Step 3: watch out for a leading @

When -d data starts with @, curl reads the rest as a file name. We sent -d '@alice', meaning the text, and curl 8.5.0 stopped with curl: option -d: error encountered when reading a file. --data-raw '@alice' sent the text as written.

Step 4: send the same data as a GET

-G moves -d data into the query string of a GET request. curl -s -G -d 'q=curl' https://httpbin.org/anything reached httpbin as a GET with q=curl in the address. That is useful for search forms and APIs that take parameters in the URL.

How do you POST JSON?

Use --json, from curl 7.82.0:

curl -s --json '{"name":"alice","admin":false}' https://httpbin.org/post

httpbin parsed the body as JSON and saw both Content-Type and Accept set to application/json. The manual describes --json as a shortcut for --data-binary plus those two headers. It does not check your JSON, so a typo still goes out.

The common mistake is JSON with -d alone. curl then labels the body as a form, and httpbin found no JSON at all:

$ curl -s -d '{"name":"alice"}' https://httpbin.org/post | jq -c '{json, form, type: .headers["Content-Type"]}'
{"json":null,"form":{"{\"name\":\"alice\"}":""},"type":"application/x-www-form-urlencoded"}

The whole object became the name of one empty form field. On a curl older than 7.82.0, send JSON with --data-binary and -H 'Content-Type: application/json'.

JSON from a file: -d removes the newlines

We saved a small JSON file of 22 bytes with line breaks and sent it twice:

$ curl -s -H 'Content-Type: application/json' -d @body.json https://httpbin.org/post | jq '.data'
"{  \"name\": \"alice\"}"
$ curl -s -H 'Content-Type: application/json' --data-binary @body.json https://httpbin.org/post | jq '.data'
"{\n  \"name\": \"alice\"\n}\n"

-d @file removed every newline, as the manual says it does. For JSON that is still valid, but for a signed payload, a CSV or anything binary it changes the data. Use --json @body.json or --data-binary @body.json to send the file as it is.

PUT and PATCH

-X changes the method word when you need one curl has no shortcut for. curl -s -X PUT --json '{"name":"bob"}' https://httpbin.org/anything arrived as a PUT with the JSON body. Use -X for PUT, PATCH and DELETE, not for POST. With -L, -X POST forces POST on every redirect, and in our redirect test that produced a POST with an empty body.

How do you upload a file?

-F sends multipart/form-data, the format of a browser form with a file field. RFC 7578 defines it. An @ before the name attaches the file, and ;type= sets its content type:

Terminal capture of the upload and proxy tests, 10 October 2026, curl 8.5.0. -F file=@report.csv with type text/csv and -F note=October reach httpbin as a file holding the CSV text and a form field. The request's Content-Type is multipart/form-data with a boundary. --form-string note=@alice arrives as the text @alice. A JSON POST through a stub proxy to an https address arrives, and the proxy log shows only CONNECT httpbin.org:443. To a plain http address it arrives as well. There the proxy log shows POST http://httpbin.org/post with Content-Type application/json.
Our own run on our server, 10 October 2026, 20:33 UTC: curl 8.5.0 on Ubuntu 24.04 and a stub proxy we wrote, listening on 127.0.0.1 with the login USER:PASS.
curl -s -F 'file=@report.csv;type=text/csv' -F 'note=October' https://httpbin.org/post

httpbin received report.csv under files and note under form. curl set the content type to multipart/form-data with a boundary, a separator line it chooses between the parts. Repeat -F for more files or fields. Two more forms are worth knowing:

  • -F 'note=<notes.txt' puts the text of a file into a normal field instead of attaching it.
  • --form-string 'note=@alice' sends a value that starts with @ or < as plain text. Our run delivered "note":"@alice".

You do not need -H 'Content-Type: multipart/form-data'. When we added it anyway, curl 8.5.0 still appended its boundary, and httpbin read the form.

Why does JSON break on Windows?

Quoting. We sent the same JSON body from three Windows shells with curl.exe 8.21.0 and read what httpbin received:

Shell and commandWhat httpbin receivedValid JSON?
Windows PowerShell 5.1: curl.exe --json '{"name":"alice"}' URL{name:alice}No
PowerShell 7.6.6: the same command{"name":"alice"}Yes
cmd.exe: curl.exe --json "{\"name\":\"alice\"}" URL{"name":"alice"}Yes
cmd.exe: curl.exe --json '{"name":"alice"}' URL'{name:alice}'No

Windows PowerShell 5.1 drops double quotes inside an argument when it starts a program like curl.exe. Microsoft's documentation says PowerShell 7.3 changed this and now passes the quotes on. JSON requires quotation marks around every name, as RFC 8259 defines it, so {name:alice} is not JSON. cmd.exe does not treat single quotes as quotes at all. A Stack Overflow question titled "Unexpected character (''' (code 39))" shows what an API answers to that.

On Windows, use PowerShell 7, escape the quotes in cmd.exe, or keep the JSON in a file and send it with --json @body.json. In Windows PowerShell 5.1, also type curl.exe, because curl there means Invoke-WebRequest.

What does a proxy see of a POST?

We sent the same JSON through a stub proxy on our server. It asked for the login USER:PASS and logged each request:

TargetWhat the proxy logged
https://httpbin.org/postCONNECT httpbin.org:443 with the login. Nothing of the POST.
http://httpbin.org/postPOST http://httpbin.org/post with the login and Content-Type: application/json.

Both posts arrived intact. Over HTTPS the proxy only opens a tunnel, and the POST travels encrypted inside it. Over plain HTTP the proxy handles the request itself and can read the body. Send anything private to https:// addresses. The flags are the usual -x and -U; how to use proxies with curl covers them. With a real gateway that is -x http://premium.hproxy.com:10000 -U USER:PASS and your own login, as our residential proxies use.

What goes wrong most often?

What you seeWhyWhat to do
The API says the body is not JSON-d labelled it as a formUse --json, or add -H 'Content-Type: application/json'.
A field arrives cut at & or a space-d sends the text as writtenUse --data-urlencode.
option -d: error encountered when reading a fileThe data starts with @Use --data-raw.
A JSON or CSV file lost its line breaks-d @file removes newlinesUse --data-binary @file or --json @file.
{name:alice} arrives from WindowsWindows PowerShell 5.1 dropped the quotesUse PowerShell 7, escaped quotes in cmd, or a file.
Unexpected character (''' (code 39))Single quotes in cmd.exeUse double quotes with \" inside.
An empty POST after a redirect-X POST together with -LDrop -X POST.

What this page could not check

Every body went to httpbin.org, which echoes what it parses; a real API may read a body differently or reject it. The Linux runs used curl 8.5.0 and the Windows quoting test used curl.exe 8.21.0, one run per shell. We did not send bodies large enough to bring in Expect: 100-continue, and no chunked uploads. The proxy was our own stub. The wording of the @ error comes from curl 8.5.0 and may differ in other versions. curl and PowerShell both change between releases, so we will check this page again by 10 January 2027.

Sources

  • The curl man page for version 8.23.0, read on 10 October 2026: curl.se/docs/manpage.html.
  • RFC 7578, Returning Values from Forms: multipart/form-data, July 2015: rfc-editor.org/rfc/rfc7578.
  • RFC 8259, The JavaScript Object Notation (JSON) Data Interchange Format, December 2017: rfc-editor.org/rfc/rfc8259.
  • Microsoft Learn, about_Parsing (arguments with quote characters), updated 29 June 2026: learn.microsoft.com.
  • Microsoft Learn, Invoke-WebRequest for Windows PowerShell 5.1, with its curl alias, updated 20 January 2026: learn.microsoft.com.
  • Stack Overflow, "Unexpected character (''' (code 39))", asked 19 July 2015, read through the Stack Exchange API on 10 October 2026.
  • Our own POST tests of 10 October 2026: curl 8.5.0 on Ubuntu 24.04 against httpbin.org and through a stub proxy, and curl.exe 8.21.0 from three Windows shells. This page's research folder keeps every transcript, capture and script from those runs.

Frequently asked questions

How do I send a POST request with curl?
Add -d with the data: curl -d 'name=alice' https://example.com/form. curl then sends a POST with the content type application/x-www-form-urlencoded, like a browser form. You do not need -X POST; -d already makes the request a POST.
How do I POST JSON with curl?
Use --json, available from curl 7.82.0: curl --json '{"name":"alice"}' https://example.com/api. It sends the body as written and sets Content-Type and Accept to application/json. On older curl, use --data-binary with -H 'Content-Type: application/json'.
Why does my JSON arrive as form data?
Because -d labels the body as a form unless you set another content type. In our test, JSON sent with -d alone reached the server as one form field holding the whole object. Use --json, or add -H 'Content-Type: application/json'.
How do I upload a file with curl?
Use -F with an @ before the file name: curl -F 'file=@report.csv' https://example.com/upload. curl sends multipart/form-data, as a browser does with a file field. Add ;type=text/csv to set the file's content type, and more -F options for more fields.
Why does my JSON break in PowerShell?
Windows PowerShell 5.1 removes the double quotes inside an argument it passes to curl.exe. In our test the JSON {"name":"alice"} arrived as {name:alice}, which is not JSON. PowerShell 7.3 and newer pass the quotes on; in cmd.exe, escape them as \".
What is the difference between -d and --data-binary?
When they read a file with @, -d strips its newlines and carriage returns, and --data-binary sends every byte. Both label the body as a form unless you set a content type. In our test, a JSON file sent with -d lost its line breaks.
How do I send a value that starts with @?
Use --data-raw, or --form-string for -F. With -d, a value that starts with @ is read as a file name; our curl 8.5.0 stopped with option -d: error encountered when reading a file.

Get proxies that are alive right now

Our free proxy list re-checks every exit every few minutes across 100+ countries, with a live last-checked time, so you copy IPs that worked moments ago, not a stale text dump. When the location has to survive a real check, the paid network holds up.

129M+ proxy checks run · 100+ countries · HTTP / HTTPS / SOCKS · re-checked every few minutes · no signup

HProxy.

Honest guides and comparisons on proxies, scraping and staying unblocked, from the team that runs the network.

RSS feed