x-www-form-urlencoded: 完全ガイド (2026年版)
application/x-www-form-urlencoded コンテンツタイプについて知っておくべきことのすべて
Hyperealで構築を始めよう
Kling、Flux、Sora、Veoなどに単一のAPIでアクセス。無料クレジットで開始、数百万規模まで拡張可能。
クレジットカード不要 • 10万人以上の開発者 • エンタープライズ対応
x-www-form-urlencoded: 完全ガイド (2026年版)
application/x-www-form-urlencoded は、HTTP において最も基本的なコンテンツタイプの1つです。HTML フォーム送信のデフォルトのエンコーディングであり、API、OAuth フロー、決済処理などで現在も広く利用されています。数十年という歴史がありますが、依然としてあらゆる場面で使用されており、その仕組みを誤解すると、特定しにくいバグの原因となります。
このガイドでは、その仕組み、使用場面、エンコーディング規則、および主要な言語によるコード例について解説します。
x-www-form-urlencoded とは?
application/x-www-form-urlencoded は、フォームデータをリクエストボディ内でキーと値のペアとしてエンコードするコンテンツタイプです。データは URL クエリパラメータと同じ形式になります。
name=John+Doe&email=john%40example.com&age=30
主な規則:
- キーと値のペアは
&で区切られる - キーと値は
=で区切られる - スペースは
+(または%20) にエンコードされる - 特殊文字はパーセントエンコーディングされる (例:
@は%40になる) - ボディ全体は単一のフラットな文字列である (ネスト不可)
HTTP リクエストの例
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
エンコーディング規則
バグを避けるためには、エンコーディング規則を理解することが不可欠です。
エンコードが必要な文字
| 文字 | エンコード後 | 理由 |
|---|---|---|
| スペース | + または %20 |
デリミタとの衝突回避 |
& |
%26 |
ペアの区切り文字 |
= |
%3D |
キーと値の区切り文字 |
+ |
%2B |
スペースとして使用されるため |
@ |
%40 |
予約済み文字 |
# |
%23 |
フラグメント識別子 |
/ |
%2F |
パス区切り文字 |
? |
%3F |
クエリ文字列マーカー |
% |
%25 |
エンコーディングのプレフィックス |
! |
%21 |
予約済み |
' |
%27 |
予約済み |
( |
%28 |
予約済み |
) |
%29 |
予約済み |
エンコードが不要な文字
英数字 (A-Z, a-z, 0-9) および非予約文字 (-, _, ., ~) はエンコード不要です。
スペースのエンコーディング: + vs %20
これはよく混乱を招くポイントです。
- form-urlencoded のボディ内: スペースは
+にエンコードされます。 - URL パスおよびクエリ文字列 (RFC 3986): スペースは
%20にエンコードされます。
form-urlencoded のボディでは + と %20 の両方が有効ですが、慣習的には + が使われます。ほとんどのライブラリはデフォルトで + を使用します。
フォームボディ: name=John+Doe (正解)
フォームボディ: name=John%20Doe (これも正解)
URL パス: /users/John%20Doe (正解)
URL パス: /users/John+Doe (間違い - + が文字通り解釈される)
コード例
JavaScript / TypeScript (Fetch API)
// URLSearchParams を使用 (推奨)
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(),
// 出力: name=John+Doe&email=john%40example.com&age=30
});
const data = await response.json();
// 短縮形: URLSearchParams を直接 body に渡す
const response = await fetch("https://api.example.com/users", {
method: "POST",
body: new URLSearchParams({
name: "John Doe",
email: "john@example.com",
age: "30",
}),
});
// body が URLSearchParams の場合、Content-Type は自動的に設定されます
Python (requests)
import requests
# requests は data= パラメータを使用すると自動的にフォームデータをエンコードします
response = requests.post(
"https://api.example.com/users",
data={
"name": "John Doe",
"email": "john@example.com",
"age": 30
}
)
print(response.json())
# urllib を使った手動エンコーディング
from urllib.parse import urlencode
body = urlencode({
"name": "John Doe",
"email": "john@example.com",
"age": 30
})
print(body)
# 出力: name=John+Doe&email=john%40example.com&age=30
cURL
# -d フラグを使用 (Content-Type は自動設定されます)
curl -X POST https://api.example.com/users \
-d "name=John+Doe" \
-d "email=john%40example.com" \
-d "age=30"
# --data-urlencode を使用 (エンコーディングを自動処理します)
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
// 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());
}
}
x-www-form-urlencoded データのパース
Node.js / Express
import express from "express";
const app = express();
// フォームデータをパースするための組み込みミドルウェア
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' }
// 注意: すべての値は文字列です。数値は手動でパースしてください。
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 と他のコンテンツタイプの比較
比較表
| 機能 | x-www-form-urlencoded | multipart/form-data | application/json |
|---|---|---|---|
| ファイルアップロード | 不可 | 可 | Base64のみ |
| ネストしたデータ | 不可 (フラット) | 不可 (フラット) | 可 (ネイティブ) |
| バイナリデータ | 不可 | 可 | Base64のみ |
| 可読性 | まあまあ | 低い | 高い |
| エンコードの負荷 | 中程度 | ファイルの場合は低い | 低い |
| ブラウザネイティブ | はい (デフォルトフォーム) | はい (enctype 指定) | JavaScript が必要 |
| 配列サポート | 慣習に基づく | 慣習に基づく | ネイティブ |
使い分けの基準
x-www-form-urlencoded を使用する場合:
- シンプルな HTML フォームの送信
- OAuth 2.0 のトークンエンドポイント (仕様で必須)
- 決済 API の送信 (Stripe, PayPal など)
- ネストのないシンプルなキー・値データ
- レガシー API との互換性
multipart/form-data を使用する場合:
- ファイルのアップロード
- バイナリデータの送信
- ファイルとフォームフィールドを混ぜて送信する場合
application/json を使用する場合:
- RESTful API エンドポイント
- ネストした、または複雑なデータ構造
- モダンなフロントエンドとバックエンド間の通信
- リクエストボディに配列やオブジェクトを含める場合
配列とネストしたデータの扱い
x-www-form-urlencoded は配列やネストしたオブジェクトをネイティブにはサポートしていませんが、一般的な慣習があります。
配列
# キーの繰り返し (最も一般的)
colors=red&colors=blue&colors=green
# ブラケット表記 (PHP スタイル)
colors[]=red&colors[]=blue&colors[]=green
# インデックス付き表記
colors[0]=red&colors[1]=blue&colors[2]=green
// JavaScript: 配列の送信
const params = new URLSearchParams();
params.append("colors", "red");
params.append("colors", "blue");
params.append("colors", "green");
console.log(params.toString());
// 出力: colors=red&colors=blue&colors=green
// 配列の読み取り
const colors = params.getAll("colors");
// ["red", "blue", "green"]
ネストしたオブジェクト
# ブラケット表記 (PHP/Rails スタイル)
user[name]=John&user[address][city]=NYC&user[address][zip]=10001
すべてのサーバーがネストしたブラケット表記をパースできるわけではありません。Express の場合、urlencoded ミドルウェアで extended: true を設定する必要があります。ネストしたデータが必要な場合は、JSON の使用を検討してください。
よくある落とし穴
1. 特殊文字のエンコード忘れ
// 間違い - 値に含まれる & がパースを壊す
const body = "company=AT&T&city=Dallas";
// パース結果: { company: "AT", T: "", city: "Dallas" }
// 正解 - & をエンコードする
const body = "company=AT%26T&city=Dallas";
// パース結果: { company: "AT&T", city: "Dallas" }
// 最善策 - URLSearchParams を使用する
const params = new URLSearchParams({ company: "AT&T", city: "Dallas" });
// 自動的にエンコードされる: company=AT%26T&city=Dallas
2. 二重エンコーディング
// 間違い - すでにエンコードされた文字列を再度エンコードする
const encoded = encodeURIComponent("John+Doe");
// 結果: "John%2BDoe" (+ が再度エンコードされる)
// 正解 - 生の値を一度だけエンコードする
const params = new URLSearchParams({ name: "John Doe" });
// 結果: name=John+Doe
3. 値を文字列以外として扱う
x-www-form-urlencoded のすべての値は文字列です。数値、真偽値、null はサーバー側で変換する必要があります。
// クライアント送信: active=true&count=5
// サーバー受信:
req.body.active // "true" (文字列であり、真偽値ではない)
req.body.count // "5" (文字列であり、数値ではない)
// 常に明示的にパースする:
const active = req.body.active === "true";
const count = parseInt(req.body.count, 10);
4. Content-Type ヘッダーの欠落
一部のフレームワークでは、Content-Type ヘッダーが自動的に設定されません。
// 間違い - Content-Type がないため、サーバーがボディをパースできない可能性がある
fetch("/api/data", {
method: "POST",
body: "name=John&age=30",
});
// 正解 - 明示的に Content-Type を指定
fetch("/api/data", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: "name=John&age=30",
});
// こちらも正解 - URLSearchParams を使うと自動設定される
fetch("/api/data", {
method: "POST",
body: new URLSearchParams({ name: "John", age: "30" }),
});
OAuth 2.0 と x-www-form-urlencoded
OAuth 2.0 の仕様では、トークンエンドポイントが x-www-form-urlencoded データを受け入れることが求められています。これは、実世界での最も一般的な用途の1つです。
// OAuth 2.0 トークン交換
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 }
結論
application/x-www-form-urlencoded はシンプルで広く理解されているエンコーディングであり、HTML フォーム、OAuth フロー、および多くの API において不可欠な存在であり続けています。覚えておくべき重要なポイントは、エンコーディングには常にライブラリ関数を使用し(手動で文字列を構築しない)、スペースのエンコーディングの違い (+ vs %20) に注意し、サーバー側では値を文字列としてパースすることです。
現代の API 開発では、複雑なデータ構造には JSON が好まれますが、シンプルなフォームや OAuth のような仕様で要求されるユースケースでは、依然として x-www-form-urlencoded が最適な選択肢となります。
テキストプロンプトから画像、動画、音声を作成するメディア生成を扱う API を構築しているなら、Hypereal AI が JSON 形式のリクエスト/レスポンスを備えたシンプルな REST API を提供しています。アプリケーションに AI メディア機能を追加するために、ぜひチェックしてみてください。
