x-www-form-urlencoded: Complete Guide (2026)
Everything you need to know about the application/x-www-form-urlencoded content type
Start Building with Hypereal
Access Kling, Flux, Sora, Veo & more through a single API. Free credits to start, scale to millions.
No credit card required • 100k+ developers • Enterprise ready
x-www-form-urlencoded: Complete Guide (2026)
application/x-www-form-urlencoded is one of the most fundamental content types in HTTP. It is the default encoding for HTML form submissions and remains widely used in APIs, OAuth flows, and payment processing. Despite being decades old, it is still everywhere -- and misunderstanding it causes subtle bugs.
This guide covers how it works, when to use it, encoding rules, and code examples in every major language.
What Is x-www-form-urlencoded?
application/x-www-form-urlencoded is a content type that encodes form data as key-value pairs in the request body. The data is formatted the same way as URL query parameters:
name=John+Doe&email=john%40example.com&age=30
Key rules:
- Key-value pairs are separated by
& - Keys and values are separated by
= - Spaces are encoded as
+(or%20) - Special characters are percent-encoded (e.g.,
@becomes%40) - The entire body is a single flat string (no nesting)
HTTP Request Example
POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 47
name=John+Doe&email=john%40example.com&age=30
Encoding Rules
Understanding the encoding rules is critical for avoiding bugs.
Characters That Must Be Encoded
| Character | Encoded As | Reason |
|---|---|---|
| Space | + or %20 |
Delimiter conflict |
& |
%26 |
Pair separator |
= |
%3D |
Key-value separator |
+ |
%2B |
Used for spaces |
@ |
%40 |
Reserved character |
# |
%23 |
Fragment identifier |
/ |
%2F |
Path separator |
? |
%3F |
Query string marker |
% |
%25 |
Encoding prefix |
! |
%21 |
Reserved |
' |
%27 |
Reserved |
( |
%28 |
Reserved |
) |
%29 |
Reserved |
Characters That Do Not Need Encoding
Alphanumeric characters (A-Z, a-z, 0-9) and the unreserved characters (-, _, ., ~) do not need encoding.
Space Encoding: + vs %20
This is a common source of confusion:
- In form-urlencoded bodies: spaces are encoded as
+ - In URL paths and query strings (RFC 3986): spaces are encoded as
%20
Both + and %20 are valid in form-urlencoded bodies, but + is the convention. Most libraries use + by default.
Form body: name=John+Doe (correct)
Form body: name=John%20Doe (also correct)
URL path: /users/John%20Doe (correct)
URL path: /users/John+Doe (WRONG - + is literal)
Code Examples
JavaScript / TypeScript (Fetch API)
// Using URLSearchParams (recommended)
const params = new URLSearchParams();
params.append("name", "John Doe");
params.append("email", "john@example.com");
params.append("age", "30");
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: params.toString(),
// Output: name=John+Doe&email=john%40example.com&age=30
});
const data = await response.json();
// Shorthand: pass URLSearchParams directly as body
const response = await fetch("https://api.example.com/users", {
method: "POST",
body: new URLSearchParams({
name: "John Doe",
email: "john@example.com",
age: "30",
}),
});
// Content-Type is set automatically when body is URLSearchParams
Python (requests)
import requests
# requests encodes form data automatically with data= parameter
response = requests.post(
"https://api.example.com/users",
data={
"name": "John Doe",
"email": "john@example.com",
"age": 30
}
)
print(response.json())
# Manual encoding with urllib
from urllib.parse import urlencode
body = urlencode({
"name": "John Doe",
"email": "john@example.com",
"age": 30
})
print(body)
# Output: name=John+Doe&email=john%40example.com&age=30
cURL
# Using -d flag (automatically sets Content-Type)
curl -X POST https://api.example.com/users \
-d "name=John+Doe" \
-d "email=john%40example.com" \
-d "age=30"
# Using --data-urlencode (handles encoding for you)
curl -X POST https://api.example.com/users \
--data-urlencode "name=John Doe" \
--data-urlencode "email=john@example.com" \
--data-urlencode "age=30"
Go
package main
import (
"fmt"
"net/http"
"net/url"
"strings"
)
func main() {
data := url.Values{}
data.Set("name", "John Doe")
data.Set("email", "john@example.com")
data.Set("age", "30")
resp, err := http.Post(
"https://api.example.com/users",
"application/x-www-form-urlencoded",
strings.NewReader(data.Encode()),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Status:", resp.Status)
}
PHP
<?php
// Using cURL
$data = http_build_query([
'name' => 'John Doe',
'email' => 'john@example.com',
'age' => 30
]);
$ch = curl_init('https://api.example.com/users');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Java
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
public class FormPost {
public static void main(String[] args) throws Exception {
String body = String.join("&",
"name=" + URLEncoder.encode("John Doe", StandardCharsets.UTF_8),
"email=" + URLEncoder.encode("john@example.com", StandardCharsets.UTF_8),
"age=30"
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpClient client = HttpClient.newHttpClient();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
Parsing x-www-form-urlencoded Data
Node.js / Express
import express from "express";
const app = express();
// Built-in middleware for parsing form data
app.use(express.urlencoded({ extended: true }));
app.post("/api/users", (req, res) => {
console.log(req.body);
// { name: 'John Doe', email: 'john@example.com', age: '30' }
// Note: all values are strings -- parse numbers manually
const age = parseInt(req.body.age, 10);
res.json({ received: req.body });
});
Python / Flask
from flask import Flask, request
app = Flask(__name__)
@app.route("/api/users", methods=["POST"])
def create_user():
name = request.form["name"]
email = request.form["email"]
age = int(request.form["age"])
return {"name": name, "email": email, "age": age}
Go (net/http)
func handler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
name := r.FormValue("name")
email := r.FormValue("email")
age := r.FormValue("age")
fmt.Fprintf(w, "Name: %s, Email: %s, Age: %s", name, email, age)
}
x-www-form-urlencoded vs. Other Content Types
Comparison Table
| Feature | x-www-form-urlencoded | multipart/form-data | application/json |
|---|---|---|---|
| File uploads | No | Yes | Base64 only |
| Nested data | No (flat key-value) | No (flat key-value) | Yes (native) |
| Binary data | No | Yes | Base64 only |
| Human readable | Somewhat | No | Yes |
| Encoding overhead | Moderate | Low for files | Low |
| Browser native | Yes (default form) | Yes (with enctype) | Requires JavaScript |
| Array support | Convention-based | Convention-based | Native |
When to Use Each
Use x-www-form-urlencoded when:
- Submitting simple HTML forms
- OAuth 2.0 token endpoints (required by spec)
- Payment API submissions (Stripe, PayPal)
- Simple key-value data without nesting
- Legacy API compatibility
Use multipart/form-data when:
- Uploading files
- Sending binary data
- Mixing files with form fields
Use application/json when:
- RESTful API endpoints
- Nested or complex data structures
- Modern frontend-to-backend communication
- Arrays and objects in the request body
Handling Arrays and Nested Data
x-www-form-urlencoded does not natively support arrays or nested objects, but there are common conventions:
Arrays
# Repeated keys (most common)
colors=red&colors=blue&colors=green
# Bracket notation (PHP-style)
colors[]=red&colors[]=blue&colors[]=green
# Indexed notation
colors[0]=red&colors[1]=blue&colors[2]=green
// JavaScript: sending arrays
const params = new URLSearchParams();
params.append("colors", "red");
params.append("colors", "blue");
params.append("colors", "green");
console.log(params.toString());
// Output: colors=red&colors=blue&colors=green
// Reading arrays
const colors = params.getAll("colors");
// ["red", "blue", "green"]
Nested Objects
# Bracket notation (PHP/Rails-style)
user[name]=John&user[address][city]=NYC&user[address][zip]=10001
Not all servers parse nested bracket notation. Express requires extended: true in the urlencoded middleware. If you need nested data, consider using JSON instead.
Common Pitfalls
1. Forgetting to Encode Special Characters
// WRONG - & in value breaks parsing
const body = "company=AT&T&city=Dallas";
// Parsed as: { company: "AT", T: "", city: "Dallas" }
// CORRECT - encode the &
const body = "company=AT%26T&city=Dallas";
// Parsed as: { company: "AT&T", city: "Dallas" }
// BEST - use URLSearchParams
const params = new URLSearchParams({ company: "AT&T", city: "Dallas" });
// Automatically encodes: company=AT%26T&city=Dallas
2. Double Encoding
// WRONG - encoding an already-encoded string
const encoded = encodeURIComponent("John+Doe");
// Result: "John%2BDoe" (the + gets encoded again)
// CORRECT - encode the raw value once
const params = new URLSearchParams({ name: "John Doe" });
// Result: name=John+Doe
3. Treating Values as Non-Strings
All values in x-www-form-urlencoded are strings. Numbers, booleans, and nulls must be converted on the server side:
// Client sends: active=true&count=5
// Server receives:
req.body.active // "true" (string, not boolean)
req.body.count // "5" (string, not number)
// Always parse explicitly:
const active = req.body.active === "true";
const count = parseInt(req.body.count, 10);
4. Missing Content-Type Header
Some frameworks do not automatically set the Content-Type header:
// WRONG - missing Content-Type, server may not parse body
fetch("/api/data", {
method: "POST",
body: "name=John&age=30",
});
// CORRECT - explicit Content-Type
fetch("/api/data", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: "name=John&age=30",
});
// ALSO CORRECT - URLSearchParams sets it automatically
fetch("/api/data", {
method: "POST",
body: new URLSearchParams({ name: "John", age: "30" }),
});
OAuth 2.0 and x-www-form-urlencoded
The OAuth 2.0 specification requires token endpoints to accept x-www-form-urlencoded data. This is one of the most common real-world uses:
// OAuth 2.0 token exchange
const response = await fetch("https://oauth.provider.com/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "authorization_code",
code: "auth_code_from_redirect",
redirect_uri: "https://yourapp.com/callback",
client_id: "your_client_id",
client_secret: "your_client_secret",
}),
});
const tokens = await response.json();
// { access_token: "...", refresh_token: "...", expires_in: 3600 }
Conclusion
application/x-www-form-urlencoded is a simple, well-understood encoding that remains essential for HTML forms, OAuth flows, and many APIs. The key things to remember: always use library functions for encoding (never build the string manually), be aware of the space encoding difference (+ vs %20), and parse values as strings on the server side.
For modern API development, JSON is generally preferred for complex data structures, but x-www-form-urlencoded remains the right choice for simple forms and specification-required use cases like OAuth.
If you are building APIs that handle media generation -- like creating images, videos, or audio from text prompts -- Hypereal AI provides a straightforward REST API with JSON request/response format. Check it out for adding AI media capabilities to your applications.
Related Articles
Start Building Today
Get 35 free credits on signup. No credit card required. Generate your first image in under 5 minutes.
