Quick verdict: Use -d '{"key":"value"}' -H "Content-Type: application/json" for JSON. Use -F "field=value" -F "file=@photo.jpg" for multipart file uploads. Use -d "name=alice&age=30" for form-encoded data. cURL adds -X POST automatically when you use -d or -F, so you rarely need it explicitly.
POST JSON
The most common pattern in 2026:
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d '{"name":"Alice","email":"alice@example.com"}'Three things to know:
-X POSTis technically optional —-dimplies POST. But explicit is clearer in scripts.-dby default setsContent-Type: application/x-www-form-urlencoded. Override with-Hfor JSON.- Wrap JSON in single quotes (Bash) so dollars and exclamations are not expanded. Inside JSON, double quotes around keys/values.
For complex JSON, read from a file:
curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d @user.jsonThe @ prefix tells cURL "read body from this file." Useful when JSON has special chars or is generated by another tool (jq, etc.).
POST Form-Encoded Data
Old-school HTML form submission:
curl -X POST https://example.com/login -d "username=alice" -d "password=secret123"Multiple -d flags concatenate with &. Equivalent to:
curl -X POST https://example.com/login -d "username=alice&password=secret123"Content-Type defaults to application/x-www-form-urlencoded. The body is exactly the encoded string (no JSON wrapping).
URL-Encoded Special Characters
If your form values have spaces, ampersands, or equal signs, use --data-urlencode:
curl -X POST https://example.com/search --data-urlencode "query=hello world & more" --data-urlencode "page=1"cURL escapes the space, ampersand, and other unsafe chars before sending. Without --data-urlencode, the unescaped & would split your value mid-string.
Multipart File Upload
Forms with file inputs use multipart/form-data. Use -F:
curl -X POST https://api.example.com/upload -F "title=Beach photo" -F "file=@/home/alice/photo.jpg"The @ in file=@path is the file-upload syntax. cURL reads the file, sets the right MIME type, and builds the multipart body for you.
Override the MIME type:
-F "file=@photo.jpg;type=image/jpeg"Set the filename in the form (different from the disk path):
-F "file=@/tmp/upload.bin;filename=document.pdf;type=application/pdf"Multiple files at once:
curl -X POST https://api.example.com/upload -F "image1=@photo1.jpg" -F "image2=@photo2.jpg"Raw POST Body (no encoding)
Sometimes you want the body sent verbatim, without cURL stripping newlines or interpreting @:
curl -X POST https://api.example.com/raw -H "Content-Type: text/plain" --data-raw "$(cat body.txt)"--data-raw is identical to -d except it does NOT interpret @ as "read from file." Useful when your body genuinely starts with an @ sign (uncommon but exists).
For binary data:
curl -X POST https://api.example.com/binary -H "Content-Type: application/octet-stream" --data-binary @image.png--data-binary @file reads the file byte-for-byte. -d @file strips line endings, which corrupts binary data.
POST with Auth
Bearer token + JSON body:
curl -X POST https://api.example.com/items -H "Authorization: Bearer eyJhbGc..." -H "Content-Type: application/json" -d '{"name":"widget"}'Basic auth + form data:
curl -X POST https://api.example.com/items -u user:pass -d "name=widget"See cURL basic authentication for all the auth options.
POST Through a Proxy
Useful for testing geo-locked APIs or routing through residential IPs:
curl -X POST https://api.example.com/data --proxy http://USER:PASS@gw.spyderproxy.com:8000 -H "Content-Type: application/json" -d '{"region":"us-east"}'For high-volume API calls behind anti-bot protection, route through Premium Residential proxies for natural-looking traffic.
Capturing the Response
By default cURL prints the body to stdout. Capture it:
# Save to file
curl -X POST https://api.example.com/data -d '...' -o response.json
# Variable in bash
RESPONSE=$(curl -X POST https://api.example.com/data -d '...')
# Get HTTP status code
STATUS=$(curl -X POST https://api.example.com/data -d '...' -o /dev/null -w "%{http_code}")Pipe through jq for JSON pretty-printing:
curl -s -X POST https://api.example.com/data -d '{}' | jq .Debugging POSTs
The -v flag shows the full request and response:
curl -v -X POST https://api.example.com/data -d '{"foo":1}'You will see lines starting with > (request) and < (response). Critical things to check:
- Content-Type matches what the API expects
- Content-Length matches your body size
- Authorization header is present and not truncated
- The exact body cURL sent (sometimes shell expansion mangles it)
Use --trace-ascii /dev/stdout for even more detail — shows the bytes cURL sent on the wire.
Related Reading
curl vs wget compares the two download tools. cURL timeout settings covers --max-time and --connect-timeout. Mastering cURL is the full reference. For the JavaScript equivalent, see cURL in JavaScript.