Web Development
HTTP Protocols
Form Data
Content-Type
Web Application Security

application/x-www-form-urlencoded or multipart/form-data?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

application/x-www-form-urlencoded and multipart/form-data are the two main Content-Type encodings for sending form data in HTTP POST requests. The URL-encoded format sends key-value pairs as a single string (key1=value1&key2=value2), which is compact for text data but cannot handle binary files. The multipart format sends each field as a separate part with its own content type, making it the only option for file uploads. Use URL-encoded for simple text forms and multipart when the form includes file inputs.

application/x-www-form-urlencoded

html
1<form method="POST" action="/login">
2    <input type="text" name="username" value="alice" />
3    <input type="password" name="password" value="s3cr3t" />
4    <button type="submit">Login</button>
5</form>

The browser sends:

 
1POST /login HTTP/1.1
2Content-Type: application/x-www-form-urlencoded
3
4username=alice&password=s3cr3t

Key-value pairs are joined by &, keys and values are separated by =, and special characters are percent-encoded (spaces become + or %20). This is the default encoding when no enctype is specified on the form.

multipart/form-data

html
1<form method="POST" action="/upload" enctype="multipart/form-data">
2    <input type="text" name="title" value="My Photo" />
3    <input type="file" name="image" />
4    <button type="submit">Upload</button>
5</form>

The browser sends:

 
1POST /upload HTTP/1.1
2Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
3
4------WebKitFormBoundary7MA4YWxkTrZu0gW
5Content-Disposition: form-data; name="title"
6
7My Photo
8------WebKitFormBoundary7MA4YWxkTrZu0gW
9Content-Disposition: form-data; name="image"; filename="photo.jpg"
10Content-Type: image/jpeg
11
12(binary data)
13------WebKitFormBoundary7MA4YWxkTrZu0gW--

Each field is a separate "part" delimited by a boundary string. File parts include the filename and content type. Binary data is sent as-is, without encoding.

Sending with JavaScript (fetch)

javascript
1// URL-encoded
2const params = new URLSearchParams();
3params.append('username', 'alice');
4params.append('password', 's3cr3t');
5
6fetch('/login', {
7    method: 'POST',
8    body: params,
9    // Content-Type is set automatically to application/x-www-form-urlencoded
10});
11
12// Multipart (with file)
13const formData = new FormData();
14formData.append('title', 'My Photo');
15formData.append('image', fileInput.files[0]);
16
17fetch('/upload', {
18    method: 'POST',
19    body: formData,
20    // Do NOT set Content-Type manually — fetch sets it with the boundary
21});

When using FormData, do not set the Content-Type header manually. The browser generates the boundary string and includes it in the header automatically.

Sending with Python (requests)

python
1import requests
2
3# URL-encoded (default for data parameter)
4response = requests.post('https://api.example.com/login', data={
5    'username': 'alice',
6    'password': 's3cr3t'
7})
8
9# Multipart (use files parameter)
10with open('photo.jpg', 'rb') as f:
11    response = requests.post('https://api.example.com/upload', files={
12        'image': ('photo.jpg', f, 'image/jpeg')
13    }, data={
14        'title': 'My Photo'
15    })

In Python's requests library, the data parameter sends URL-encoded data. The files parameter switches to multipart encoding automatically.

Sending with cURL

bash
1# URL-encoded (default for -d)
2curl -X POST https://api.example.com/login \
3    -d "username=alice&password=s3cr3t"
4
5# Multipart (use -F)
6curl -X POST https://api.example.com/upload \
7    -F "title=My Photo" \
8    -F "[email protected]"

cURL's -d flag sends URL-encoded data. The -F flag sends multipart data. The @ prefix in -F reads a file from disk.

Server-Side Parsing

python
1# Flask (Python)
2from flask import Flask, request
3
4app = Flask(__name__)
5
6@app.route('/login', methods=['POST'])
7def login():
8    # Works for both URL-encoded and multipart text fields
9    username = request.form['username']
10    return f'Hello {username}'
11
12@app.route('/upload', methods=['POST'])
13def upload():
14    title = request.form['title']
15    image = request.files['image']
16    image.save(f'uploads/{image.filename}')
17    return f'Uploaded {image.filename}'
javascript
1// Express.js (Node.js)
2const express = require('express');
3const multer = require('multer');
4const upload = multer({ dest: 'uploads/' });
5
6const app = express();
7app.use(express.urlencoded({ extended: true }));
8
9app.post('/login', (req, res) => {
10    res.send(`Hello ${req.body.username}`);
11});
12
13app.post('/upload', upload.single('image'), (req, res) => {
14    res.send(`Uploaded ${req.file.originalname}`);
15});

Most web frameworks parse both encodings transparently. File uploads require multipart-specific middleware (like multer in Express).

Comparison Table

FeatureURL-encodedMultipart
Default for formsYesNo (requires enctype)
File uploadsNoYes
Binary dataNo (must base64 encode)Yes (native)
Encoding overhead~3x for non-ASCIIBoundary per field
Body sizeCompact for textLarger for text-only forms
Content-Typeapplication/x-www-form-urlencodedmultipart/form-data; boundary=...

Common Pitfalls

  • Using URL-encoded for file uploads: File data cannot be sent with URL encoding unless you base64-encode it first, which inflates the size by 33%. Always use multipart/form-data for file inputs.
  • Setting Content-Type manually for multipart: When using fetch with FormData or requests with files, do not set the Content-Type header manually. The library generates the boundary string and must include it in the header. Setting it manually omits the boundary, causing parse failures.
  • Percent-encoding overhead for binary data: URL encoding replaces each non-ASCII byte with %XX, tripling the data size. For binary or non-ASCII heavy payloads, multipart is far more efficient.
  • Forgetting enctype on HTML forms: Without enctype="multipart/form-data", the form defaults to URL encoding. File inputs submit only the filename, not the file contents.
  • Maximum URL-encoded body size: Some servers and proxies limit the body size for URL-encoded data (e.g., Tomcat defaults to 2MB). Multipart uploads typically have higher or configurable limits. Check server configuration for large form submissions.

Summary

  • Use application/x-www-form-urlencoded for simple text forms (login, search, settings)
  • Use multipart/form-data when the form includes file uploads or binary data
  • URL-encoded is the default — no enctype needed on the HTML form
  • Multipart requires enctype="multipart/form-data" on the form tag
  • Do not set Content-Type manually when sending FormData in JavaScript — the browser adds the boundary
  • Server frameworks parse both formats through request.form (text) and request.files (uploads)

Course illustration
Course illustration

All Rights Reserved.