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.

FlagContent-Type set@ reads a fileNewlines stripped
-d / --dataapplication/x-www-form-urlencodedyesyes
--data-rawapplication/x-www-form-urlencodednoyes
--data-binaryapplication/x-www-form-urlencodedyesno
--data-urlencodeapplication/x-www-form-urlencodedyesno
--jsonapplication/json + Acceptyesno

-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.

SyntaxSends
--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/v1

Use --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 b reaches the server as a malformed request line, so either encode by hand or let -G --data-urlencode do it.