Multipart Form Data: Complete Guide (2026)
Everything you need to know about multipart/form-data in HTTP requests
Hypereal로 구축 시작하기
단일 API를 통해 Kling, Flux, Sora, Veo 등에 액세스하세요. 무료 크레딧으로 시작하고 수백만으로 확장하세요.
신용카드 불필요 • 10만 명 이상의 개발자 • 엔터프라이즈 지원
Multipart Form Data: Complete Guide (2026)
Multipart form data (multipart/form-data) is the standard way to send files and mixed data types in HTTP requests. Whether you are uploading images to an API, submitting a form with file attachments, or sending binary data alongside text fields, multipart form data is the mechanism that makes it work.
This guide covers everything developers need to know: how it works under the hood, how to send it in every major language and tool, common pitfalls, and best practices.
What Is Multipart Form Data?
multipart/form-data is a MIME content type used to encode form data that includes files, binary data, or a mix of text and binary content. It was originally designed for HTML form submissions but is now widely used in REST APIs.
When to Use Multipart Form Data
| Use Case | Content Type | When to Use |
|---|---|---|
| Uploading files | multipart/form-data |
File uploads of any type |
| Form with files + text | multipart/form-data |
Mixed file and text fields |
| Sending JSON only | application/json |
API requests without files |
| Sending plain text only | application/x-www-form-urlencoded |
Simple key-value form data |
| Sending binary data only | application/octet-stream |
Single binary file, no metadata |
Rule of thumb: Use multipart/form-data whenever your request includes at least one file. Use application/json for data-only requests.
How Multipart Form Data Works
A multipart request splits the body into multiple "parts," separated by a unique boundary string. Each part has its own headers and content.
Raw HTTP Request Structure
POST /api/upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="title"
My Photo
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="description"
A sunset photo taken in Hawaii
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="sunset.jpg"
Content-Type: image/jpeg
<binary file data>
------WebKitFormBoundary7MA4YWxkTrZu0gW--
Key Components
| Component | Purpose | Example |
|---|---|---|
| Boundary | Separates each part | ----WebKitFormBoundary7MA4YWxkTrZu0gW |
| Content-Disposition | Names the field and filename | form-data; name="file"; filename="photo.jpg" |
| Content-Type (per part) | MIME type of the part | image/jpeg, application/pdf |
| Part body | The actual data | Text string or binary content |
The boundary is a random string that must not appear in any of the part bodies. Libraries generate this automatically -- you almost never need to set it manually.
Sending Multipart Form Data: Code Examples
cURL
# Upload a file with metadata
curl -X POST https://api.example.com/upload \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@/path/to/photo.jpg" \
-F "title=My Photo" \
-F "description=A sunset photo"
# Upload multiple files
curl -X POST https://api.example.com/upload \
-F "files=@photo1.jpg" \
-F "files=@photo2.jpg" \
-F "album=Vacation"
# Specify MIME type explicitly
curl -X POST https://api.example.com/upload \
-F "file=@document.pdf;type=application/pdf" \
-F "title=My Document"
Key points:
-Fflag tells cURL to usemultipart/form-data@prefix reads from a file- Without
@, the value is sent as a text field
Python (requests)
import requests
# Upload a single file
url = "https://api.example.com/upload"
headers = {"Authorization": "Bearer YOUR_TOKEN"}
with open("photo.jpg", "rb") as f:
files = {"file": ("photo.jpg", f, "image/jpeg")}
data = {"title": "My Photo", "description": "A sunset photo"}
response = requests.post(url, headers=headers, files=files, data=data)
print(response.json())
# Upload multiple files
files = [
("files", ("photo1.jpg", open("photo1.jpg", "rb"), "image/jpeg")),
("files", ("photo2.jpg", open("photo2.jpg", "rb"), "image/jpeg")),
]
data = {"album": "Vacation"}
response = requests.post(url, headers=headers, files=files, data=data)
# Send JSON alongside a file (common API pattern)
import json
files = {"file": ("data.json", json.dumps({"key": "value"}), "application/json")}
data = {"metadata": "some info"}
response = requests.post(url, files=files, data=data)
Important: Do NOT set Content-Type manually when using the files parameter. The requests library sets it automatically with the correct boundary string.
JavaScript (Fetch API)
// Browser: Upload a file from an input element
const form = document.querySelector("#uploadForm");
const fileInput = document.querySelector("#fileInput");
const formData = new FormData();
formData.append("file", fileInput.files[0]);
formData.append("title", "My Photo");
formData.append("description", "A sunset photo");
const response = await fetch("https://api.example.com/upload", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_TOKEN",
// Do NOT set Content-Type -- the browser sets it with the boundary
},
body: formData,
});
const result = await response.json();
console.log(result);
// Upload multiple files
const formData = new FormData();
const files = fileInput.files; // FileList from input[multiple]
for (const file of files) {
formData.append("files", file);
}
formData.append("album", "Vacation");
const response = await fetch("/api/upload", {
method: "POST",
body: formData,
});
Node.js (native fetch, Node 18+)
import { readFile } from "fs/promises";
import path from "path";
const fileBuffer = await readFile("photo.jpg");
const blob = new Blob([fileBuffer], { type: "image/jpeg" });
const formData = new FormData();
formData.append("file", blob, "photo.jpg");
formData.append("title", "My Photo");
const response = await fetch("https://api.example.com/upload", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_TOKEN",
},
body: formData,
});
const result = await response.json();
Go
package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
func main() {
// Create a buffer and multipart writer
var buf bytes.Buffer
writer := multipart.NewWriter(&buf)
// Add a file field
file, _ := os.Open("photo.jpg")
defer file.Close()
part, _ := writer.CreateFormFile("file", "photo.jpg")
io.Copy(part, file)
// Add text fields
writer.WriteField("title", "My Photo")
writer.WriteField("description", "A sunset photo")
writer.Close()
// Send the request
req, _ := http.NewRequest("POST", "https://api.example.com/upload", &buf)
req.Header.Set("Content-Type", writer.FormDataContentType())
req.Header.Set("Authorization", "Bearer YOUR_TOKEN")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
Rust (reqwest)
use reqwest::multipart;
use tokio::fs;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let file_bytes = fs::read("photo.jpg").await?;
let file_part = multipart::Part::bytes(file_bytes)
.file_name("photo.jpg")
.mime_str("image/jpeg")?;
let form = multipart::Form::new()
.part("file", file_part)
.text("title", "My Photo")
.text("description", "A sunset photo");
let client = reqwest::Client::new();
let response = client
.post("https://api.example.com/upload")
.header("Authorization", "Bearer YOUR_TOKEN")
.multipart(form)
.send()
.await?;
println!("{}", response.text().await?);
Ok(())
}
Receiving Multipart Form Data (Server Side)
Express.js (with Multer)
import express from "express";
import multer from "multer";
const app = express();
const upload = multer({ dest: "uploads/" });
// Single file upload
app.post("/upload", upload.single("file"), (req, res) => {
console.log(req.file); // File info
console.log(req.body.title); // Text field
res.json({ success: true, filename: req.file.filename });
});
// Multiple files
app.post("/upload-many", upload.array("files", 10), (req, res) => {
console.log(req.files); // Array of file objects
res.json({ success: true, count: req.files.length });
});
app.listen(3000);
Python (FastAPI)
from fastapi import FastAPI, File, UploadFile, Form
app = FastAPI()
@app.post("/upload")
async def upload_file(
file: UploadFile = File(...),
title: str = Form(...),
description: str = Form(default="")
):
contents = await file.read()
return {
"filename": file.filename,
"size": len(contents),
"title": title,
"content_type": file.content_type
}
@app.post("/upload-many")
async def upload_files(
files: list[UploadFile] = File(...),
album: str = Form(...)
):
return {
"album": album,
"files": [f.filename for f in files],
"count": len(files)
}
Go (standard library)
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// Parse multipart form with 32MB max memory
r.ParseMultipartForm(32 << 20)
// Get the file
file, header, _ := r.FormFile("file")
defer file.Close()
// Get text fields
title := r.FormValue("title")
fmt.Fprintf(w, "Uploaded: %s (%d bytes), Title: %s",
header.Filename, header.Size, title)
}
Common Mistakes and How to Avoid Them
1. Setting Content-Type Manually
The most common mistake is manually setting the Content-Type header. This breaks the request because the boundary string will not match.
# WRONG - Do not set Content-Type manually
requests.post(url, headers={"Content-Type": "multipart/form-data"}, files=files)
# CORRECT - Let the library set it
requests.post(url, files=files)
// WRONG - Do not set Content-Type manually
fetch(url, {
headers: { "Content-Type": "multipart/form-data" },
body: formData,
});
// CORRECT - Omit Content-Type, browser sets it with boundary
fetch(url, { body: formData });
2. Forgetting to Open Files in Binary Mode
# WRONG - Text mode corrupts binary files
files = {"file": open("photo.jpg", "r")}
# CORRECT - Always use binary mode
files = {"file": open("photo.jpg", "rb")}
3. Not Closing File Handles
# WRONG - File handle leaks
files = {"file": open("photo.jpg", "rb")}
requests.post(url, files=files)
# File handle is never closed
# CORRECT - Use context manager
with open("photo.jpg", "rb") as f:
files = {"file": f}
requests.post(url, files=files)
4. Mixing data and json Parameters (Python)
# WRONG - json parameter conflicts with files
requests.post(url, files=files, json={"title": "Photo"})
# CORRECT - Use data parameter for text fields alongside files
requests.post(url, files=files, data={"title": "Photo"})
Multipart vs. Base64 Encoding
Some APIs accept files as base64-encoded strings in JSON instead of multipart form data. Here is how they compare:
| Factor | Multipart Form Data | Base64 in JSON |
|---|---|---|
| Size overhead | ~0% (raw binary) | ~33% (base64 encoding) |
| Streaming support | Yes | No (must encode entire file) |
| Standard | HTTP native | Custom per API |
| Browser support | Native (FormData) | Requires manual encoding |
| Server parsing | Built-in middleware | Custom parsing |
| File size limits | Large files supported | Impractical for large files |
When to use multipart: File uploads, large files, multiple files, standard REST APIs.
When to use base64: Small files (<1 MB), APIs that require JSON-only payloads, inline image data.
File Size Limits
Most servers and platforms impose limits on multipart upload sizes:
| Platform/Server | Default Limit | Configurable |
|---|---|---|
| Express.js (Multer) | 1 MB | Yes (limits: { fileSize: X }) |
| Nginx | 1 MB | Yes (client_max_body_size) |
| Apache | 2 MB | Yes (LimitRequestBody) |
| FastAPI/Uvicorn | No hard limit | Yes (middleware) |
| AWS API Gateway | 10 MB | Fixed |
| Cloudflare | 100 MB (Pro) | Plan-dependent |
| Vercel | 4.5 MB (serverless) | Fixed |
Always check your server and infrastructure limits when building file upload features.
Frequently Asked Questions
What is the difference between multipart/form-data and application/x-www-form-urlencoded?
application/x-www-form-urlencoded encodes all data as URL-encoded key-value pairs. It cannot handle files or binary data efficiently. multipart/form-data sends each field as a separate part, supporting files and binary data natively.
Can I send JSON in a multipart request?
Yes. You can include a part with Content-Type: application/json containing your JSON data alongside file parts. Some APIs use this pattern for sending structured metadata with file uploads.
Why is my file upload returning 413 (Payload Too Large)?
Your server or reverse proxy has a file size limit that your upload exceeds. Check Nginx's client_max_body_size, Express's Multer limits, or your hosting platform's limits.
Should I use multipart/form-data or base64 for an API? Use multipart for file uploads over 1 MB or when streaming is important. Use base64 for small inline images or when your API architecture requires JSON-only payloads.
Can I upload files with a GET request? No. GET requests do not have a body in standard HTTP. File uploads must use POST, PUT, or PATCH methods.
Wrapping Up
Multipart form data is a fundamental HTTP mechanism that every developer encounters when building file upload features or working with media APIs. The key rules are simple: use it whenever files are involved, let your HTTP library set the Content-Type header, open files in binary mode, and always close file handles.
If you are building applications that process uploaded media with AI -- image generation, video creation, or audio synthesis -- try Hypereal AI free -- 35 credits, no credit card required. Hypereal's API accepts both multipart form data and JSON payloads, making it straightforward to integrate AI media generation into any upload workflow.
