Two formats cover almost every case: application/x-www-form-urlencoded for simple key/value fields, and multipart/form-data when you’re uploading files. Here’s how to send each with fetch, Python, and cURL — and how to pick.
For plain key/value data, use application/x-www-form-urlencoded. URLSearchParams builds the body and encodes each value for you:
const body = new URLSearchParams();
body.set('username', 'alice');
body.set('message', 'Hello, World!');
fetch('https://api.example.com/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body, // URLSearchParams sets the encoding; fetch sends "username=alice&message=Hello%2C+World%21"
});
You don’t set the header manually when passing URLSearchParams to fetch in the browser — but setting it explicitly (as above) is harmless and clear.
When you’re sending files, use FormData, which produces multipart/form-data. Let the browser set the Content-Type (it adds the required boundary) — do not set it yourself:
const form = new FormData();
form.append('username', 'alice');
form.append('avatar', fileInput.files[0]); // a File object
fetch('https://api.example.com/upload', {
method: 'POST',
body: form, // no Content-Type header — the browser adds multipart/form-data; boundary=...
});
| Use | Format | JS API |
|---|---|---|
| Simple key/value fields | x-www-form-urlencoded | URLSearchParams |
| Files / binary | multipart/form-data | FormData |
| JSON API | application/json | JSON.stringify |
With requests, pass data= for url-encoded, files= for multipart:
import requests
# url-encoded
requests.post(url, data={'username': 'alice', 'message': 'Hello, World!'})
# multipart (file upload)
requests.post(url, files={'avatar': open('a.png','rb')}, data={'username': 'alice'})
# url-encoded — --data-urlencode encodes each field
curl -X POST https://api.example.com/submit \
--data-urlencode "username=alice" \
--data-urlencode "message=Hello, World!"
# multipart (file)
curl -X POST https://api.example.com/upload \
-F "username=alice" -F "avatar=@a.png"
The x-www-form-urlencoded format is defined by the WHATWG URL Standard (a space becomes +); multipart/form-data is defined by RFC 7578. They’re different formats for different jobs, not interchangeable.
The form encoder builds a url-encoded body from key/value pairs and gives you a matching cURL command to test with.
Written and maintained by the urlencodedecode.com team. Every technical claim on this page is verified against primary sources — the RFCs (3986, 3629, 4648, 7578), the WHATWG URL Standard, and official vendor or language documentation — rather than second-hand summaries. When a source contradicts a common assumption, we follow the source and note the discrepancy. Corrections: contactus@urlencodedecode.com.
Build the body with URLSearchParams for simple fields and set Content-Type to application/x-www-form-urlencoded, or use FormData for file uploads and let the browser set the Content-Type. Pass the body to fetch with method: 'POST'.
x-www-form-urlencoded (built with URLSearchParams) is for simple key/value fields and encodes a space as +. multipart/form-data (built with FormData) is for file and binary uploads and carries each part with its own boundary. Use urlencoded for text fields, multipart for files.
Because multipart/form-data requires a boundary string in the Content-Type header, and the browser generates it when it serialises the FormData. If you set the header yourself you'll omit or mismatch the boundary and the server can't parse the body.
With the requests library: requests.post(url, data={...}) sends application/x-www-form-urlencoded, and requests.post(url, files={...}) sends multipart/form-data for file uploads. Pass both data= and files= together to include text fields alongside a file.
Use --data-urlencode "key=value" for each field to send url-encoded data (it encodes special characters), or -F "key=value" and -F "file=@path" to send multipart/form-data for uploads.