curl Cheatsheet
Request Bodies and JSON
Use this curl reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
The body flags
Five options put bytes in the request body, and the differences bite. All of them
imply POST unless you also pass -X.
| Flag | Content-Type set | @ reads a file | Newlines stripped |
|---|---|---|---|
-d / --data | application/x-www-form-urlencoded | yes | yes |
--data-raw | application/x-www-form-urlencoded | no | yes |
--data-binary | application/x-www-form-urlencoded | yes | no |
--data-urlencode | application/x-www-form-urlencoded | yes | no |
--json | application/json + Accept | yes | no |
-d is a form flag. It always labels the body
application/x-www-form-urlencoded, even when you hand it JSON, which is the
single most common curl mistake against a JSON API.
# wrong: JSON body, form content type, server 415s or misparses curl -d '{"name":"ada"}' https://api.example.com/users # right, the explicit way curl -H 'Content-Type: application/json' \ -d '{"name":"ada"}' https://api.example.com/users # right, the short way curl --json '{"name":"ada"}' https://api.example.com/users
--json
Added in curl 7.82.0. It is exactly a shortcut for three options, nothing more.
--json <data> == --data-binary <data>
--header "Content-Type: application/json"
--header "Accept: application/json"It does not validate the JSON. Repeating it concatenates the pieces rather
than sending two bodies, and a later -H overrides either header it set.
curl --json '{"a":1}' https://ex.com/v1 curl --json @payload.json https://ex.com/v1 # from a file jq -n '{a:1}' | curl --json @- https://ex.com/v1 # from stdin curl --json '{"a":' --json '1}' https://ex.com/v1 # concatenated curl --json '{}' -H 'Accept: text/plain' https://ex.com/v1 # override
Quoting JSON in a shell
This is where the copy-paste breaks. In bash and zsh, single quotes keep
everything literal, and double quotes let $, backticks, and \ expand.
# single quotes: safe, but you cannot interpolate a variable curl --json '{"role":"admin","cost":"$5"}' https://ex.com/v1 # double quotes: JSON quotes must be escaped, and $5 would expand curl --json "{\"name\":\"$USER\"}" https://ex.com/v1 # cleanest with variables: build the JSON with jq, then pipe it jq -nc --arg n "$USER" '{name:$n}' | curl --json @- https://ex.com/v1 # a heredoc keeps multi-line JSON readable curl --json @- https://ex.com/v1 <<'JSON' { "name": "ada", "tags": ["math", "engines"] } JSON
cmd.exe has no single-quote rule at all, so on Windows either use a file with
--json @payload.json or double the inner quotes. A file is the portable answer.
Form-encoded bodies
For a genuine HTML-form POST, -d repeated is what you want. curl joins the
pieces with & and sends them verbatim, so you own the encoding.
curl -d 'user=ada' -d 'lang=en' https://ex.com/login curl -d @body.txt https://ex.com/login # newlines stripped
--data-urlencode does the percent-encoding, which matters for anything with
&, =, +, a space, or a non-ASCII character.
| Syntax | Sends |
|---|---|
--data-urlencode 'content' | Encoded content, no name |
--data-urlencode '=content' | Encoded content, the = is dropped |
--data-urlencode 'name=content' | name= plus encoded content |
--data-urlencode '@file' | Encoded file contents |
--data-urlencode 'name@file' | name= plus encoded file contents |
curl --data-urlencode 'q=a & b' --data-urlencode 'note@readme.txt' \ https://ex.com/search
The name half is assumed to already be URL safe, curl only encodes the value.
Binary and exact bytes
--data-binary is the only body flag that sends a file byte for byte. -d @f
strips carriage returns and newlines, which silently corrupts a signature, a
protobuf, or a compressed payload.
curl --data-binary @image.png \
-H 'Content-Type: image/png' https://ex.com/upload
curl --data-binary @body.json \
-H 'Content-Type: application/json' https://ex.com/v1
gzip -c body.json | curl --data-binary @- \
-H 'Content-Encoding: gzip' \
-H 'Content-Type: application/json' https://ex.com/v1Use --data-raw when the payload legitimately starts with @ and you do not
want a file read.
curl --data-raw '@notafile' https://ex.com/echo
Query strings with -G
-G / --get moves everything you passed with -d or --data-urlencode into
the query string and switches the method back to GET. It is the readable way to
build an encoded query.
curl -G https://ex.com/search \ --data-urlencode 'q=site reliability' \ --data-urlencode 'limit=20' # GET /search?q=site%20reliability&limit=20
--url-query (curl 7.87.0) appends to the query string without touching the
method, so it composes with a POST body.
curl --url-query 'trace=1' --json '{"a":1}' https://ex.com/v1
curl does not encode the URL you type.
?q=a breaches the server as a malformed request line, so either encode by hand or let-G --data-urlencodedo it.