URL encoding in cURL
cURL is the most popular HTTP command-line tool, and it knows about URL encoding — but it doesn’t encode automatically. Slash-newcomer mistakes like curl "https://api.example.com/search?q=hello world" fail silently or produce unexpected results.
This article covers cURL’s URL encoding options, how to build correctly encoded GET and POST requests, and the common pitfalls.
The --data-urlencode flag
The simplest correct approach uses --data-urlencode:
curl --data-urlencode "q=Hello, World!" \
--data-urlencode "lang=en" \
-G https://api.example.com/search
The -G (or --get) flag tells cURL to make this a GET request with the data appended as a query string instead of sent as a POST body. cURL encodes each key/value pair correctly.
Result: cURL makes a GET request to:
https://api.example.com/search?q=Hello%2C%20World%21&lang=en
(actually with + for the space — let me verify this and add the correct details)
How --data-urlencode encodes data
cURL’s --data-urlencode produces RFC 3986 strict encoding by default. Space becomes %20, exclamation becomes %21, comma becomes %2C. This is the modern standard.
The flag has four forms based on what you pass to it:
# Plain value (just URL-encodes the whole thing)
curl --data-urlencode "Hello, World!" https://example.com
# key=value (URL-encodes only the value, not the key)
curl --data-urlencode "q=Hello, World!" https://example.com
# key@filename (reads value from file, URL-encodes it)
curl --data-urlencode "message@email.txt" https://example.com
# =value (URL-encodes the value, no key prefix)
curl --data-urlencode "=Hello, World!" https://example.com
POST requests with URL-encoded body
Without -G, the same flags become a POST request with application/x-www-form-urlencoded body:
curl -X POST https://api.example.com/submit \
--data-urlencode "name=John Doe" \
--data-urlencode "message=Hello, World!"
# Sends:
# POST /submit HTTP/1.1
# Content-Type: application/x-www-form-urlencoded
# Content-Length: ...
#
# name=John%20Doe&message=Hello%2C%20World%21
The wrong way: --data (without -urlencode)
The plain -d or --data flag does NOT encode:
# Wrong
curl -d "q=Hello, World!" https://api.example.com/search
# This sends literal "q=Hello, World!" — the comma and space are unencoded.
# Depending on the server, this either works (forgiving servers) or fails
# with a 400 (strict ones).
If you’re building a one-off script, use --data-urlencode instead of -d whenever your value might contain special characters.
GET requests: -G changes everything
The -G flag is required to make --data-urlencode produce a GET request. Without it, the data goes in the request body (which is unusual for GET):
# Without -G: produces a GET... with a body? Rarely what you want.
curl --data-urlencode "q=hello" https://api.example.com/search
# With -G: produces a clean GET with query string
curl --data-urlencode "q=hello" -G https://api.example.com/search
Encoding the URL itself
If the base URL contains special characters in the path, you have to encode them yourself before passing to cURL:
# A path with a space
curl "https://example.com/My Document"
# cURL passes this through literally — most servers will accept it
# (browsers do, too) but it’s technically invalid
# Encoded version (more reliable)
curl "https://example.com/My%20Document"
cURL doesn’t auto-encode the URL itself — only the data parameters when you use --data-urlencode.
The shell-quoting trap
If you don’t quote your URLs, the shell breaks them up at spaces and special chars:
# Wrong — the & backgrounds the curl process
curl https://example.com/search?q=hello&lang=en
# Right — quote the whole URL
curl "https://example.com/search?q=hello&lang=en"
# Or single-quote (no variable expansion)
curl 'https://example.com/search?q=hello&lang=en'
Always quote URLs in shell commands.
Reading values from files
The @filename syntax reads a value from a file (useful for large inputs):
# message.txt contains: Hello, World! <script>alert("xss")</script>
curl --data-urlencode "message@message.txt" -G https://api.example.com/log
# cURL reads message.txt, URL-encodes the content, and sends it as ?message=...
JSON bodies (when URL encoding doesn’t apply)
If your API takes JSON, you don’t want URL encoding at all — JSON has its own encoding:
curl -X POST https://api.example.com/submit \
-H "Content-Type: application/json" \
-d '{"name": "John Doe", "message": "Hello, World!"}'
Note: -d (not --data-urlencode) and the Content-Type header set explicitly.
Common cURL URL encoding mistakes
Mistake 1: Using --data instead of --data-urlencode for query strings
# Wrong — special chars in user input break the URL
curl "https://api.example.com/search?q=$(echo $user_input)"
# Right
curl --data-urlencode "q=$user_input" -G "https://api.example.com/search"
Mistake 2: Forgetting -G for GET requests
Without -G, cURL puts the data in the request body, making a POST-ish GET request that confuses some servers.
Mistake 3: Not quoting URLs
The most common mistake. Always wrap URLs in "..." or '...' when calling cURL from a shell.
Quick reference
| Goal | Flag |
|---|---|
| GET with query string | --data-urlencode "k=v" -G URL |
| POST form-encoded body | --data-urlencode "k=v" URL |
| POST JSON body | -H "Content-Type: application/json" -d '...' |
| Read value from file | --data-urlencode "k@file" |
| Don’t encode (raw POST) | -d "raw data" |
The rule: when your data might contain spaces, special characters, or non-ASCII, use --data-urlencode. Add -G for GET. Quote everything in your shell.
Found this useful? Try the URL decoder, the URL encoder, or browse all tools.