Skip to main content

RFC 7578 - Returning Values from Forms: multipart/form-data

Publication Date: July 2015
Status: Standards Track
Author: L. Masinter (Adobe)
Obsoletes: RFC 2388


Abstract

This specification defines the multipart/form-data media type, which can be used by a wide variety of applications and transported by a wide variety of protocols as a way of returning a set of values as the result of a user filling out a form. This document obsoletes RFC 2388.


Contents


Why multipart/form-data?

Limitations of application/x-www-form-urlencoded

Default form encoding:
Content-Type: application/x-www-form-urlencoded

Data format:
name=Alice&age=30&city=New%20York

Problems:
❌ Text only
❌ Binary data requires Base64 encoding (33% size increase)
❌ Inefficient for large files
❌ No multi-file support

Advantages of multipart/form-data

Content-Type: multipart/form-data; boundary=----WebKitFormBoundary

Benefits:
✅ Supports binary data (no encoding needed)
✅ Supports file uploads
✅ Supports multiple files
✅ Independent encoding per field
✅ Efficient large file transfer

Basic Format

Message Structure

POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 345

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="username"

Alice
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
Content-Type: image/jpeg

[Binary image data]
------WebKitFormBoundary7MA4YWxkTrZu0gW--

Key Components

1. Boundary (Delimiter)

Content-Type: multipart/form-data; boundary=----WebKitFormBoundary

Purpose: Separates different fields
Requirement: Must not appear in data
Format: Usually a random string

2. Part

Each field is a Part:
------WebKitFormBoundary
Content-Disposition: form-data; name="fieldname"
[blank line]
[field value]

3. Content-Disposition

Required header:
Content-Disposition: form-data; name="field_name"

For file fields:
Content-Disposition: form-data; name="file"; filename="example.txt"

HTML Form Examples

Basic File Upload

<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="text" name="username" value="Alice">
<input type="file" name="avatar">
<button type="submit">Upload</button>
</form>

Generated HTTP Request:

POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----Boundary123

------Boundary123
Content-Disposition: form-data; name="username"

Alice
------Boundary123
Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
Content-Type: image/jpeg

[Binary image data]
------Boundary123--

Multiple File Upload

<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="files" multiple>
<button type="submit">Upload</button>
</form>

Generated Request:

------Boundary123
Content-Disposition: form-data; name="files"; filename="file1.txt"
Content-Type: text/plain

[File 1 content]
------Boundary123
Content-Disposition: form-data; name="files"; filename="file2.pdf"
Content-Type: application/pdf

[File 2 content]
------Boundary123--

Programming Implementation

JavaScript (Fetch API)

// Method 1: Using FormData (Recommended)
const formData = new FormData();
formData.append('username', 'Alice');
formData.append('avatar', fileInput.files[0]);

fetch('/upload', {
method: 'POST',
body: formData
// Don't set Content-Type manually! Browser will add boundary automatically
});

// Method 2: Multiple file upload
const files = document.querySelector('input[type="file"]').files;
const formData = new FormData();

for (let file of files) {
formData.append('files', file);
}

fetch('/upload', {
method: 'POST',
body: formData
});

Python (requests)

import requests

# Single file upload
files = {'avatar': open('photo.jpg', 'rb')}
data = {'username': 'Alice'}

response = requests.post('https://example.com/upload',
files=files,
data=data)

# Multiple file upload
files = [
('files', ('file1.txt', open('file1.txt', 'rb'), 'text/plain')),
('files', ('file2.pdf', open('file2.pdf', 'rb'), 'application/pdf'))
]

response = requests.post('https://example.com/upload', files=files)

Node.js (Express)

const express = require('express');
const multer = require('multer');

const app = express();
const upload = multer({ dest: 'uploads/' });

// Single file
app.post('/upload', upload.single('avatar'), (req, res) => {
console.log(req.file); // File info
console.log(req.body); // Other fields
res.send('Upload successful');
});

// Multiple files
app.post('/upload-multi', upload.array('files', 10), (req, res) => {
console.log(req.files); // File array
res.send(`Uploaded ${req.files.length} files`);
});

PHP

<?php
// Handle upload
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Regular field
$username = $_POST['username'];

// File field
if (isset($_FILES['avatar'])) {
$file = $_FILES['avatar'];
$filename = $file['name'];
$tmpPath = $file['tmp_name'];
$size = $file['size'];
$type = $file['type'];

// Move file
move_uploaded_file($tmpPath, "uploads/$filename");
echo "Upload successful: $filename";
}
}
?>

Field Details

Content-Disposition

Syntax:

Content-Disposition: form-data; name="field_name"; filename="file_name"

Parameters:

ParameterRequiredDescription
nameForm field name
filenameFor filesOriginal filename
filename*RFC 2231 encoded filename

Examples:

Regular field:
Content-Disposition: form-data; name="username"

File field:
Content-Disposition: form-data; name="file"; filename="report.pdf"

Internationalized filename (RFC 2231):
Content-Disposition: form-data; name="file";
filename*=UTF-8''%E6%8A%A5%E5%91%8A.pdf

Content-Type

Optional header (defaults to text/plain):

Text field (usually omitted):
Content-Disposition: form-data; name="message"

Hello World

File field (recommended):
Content-Disposition: form-data; name="file"; filename="image.png"
Content-Type: image/png

[PNG binary data]

Common MIME Types:

text/plain
image/jpeg, image/png, image/gif
application/pdf
application/zip
video/mp4
audio/mpeg

Boundary (Delimiter)

Generation Rules

Requirements:
1. Must not appear in data
2. 70 characters or less (recommended)
3. Composed of ASCII printable characters

Common formats:
----WebKitFormBoundary[random characters]
----Boundary[timestamp][random number]

Examples:
----WebKitFormBoundary7MA4YWxkTrZu0gW
----1234567890123456789012345678

Usage

In data:
------WebKitFormBoundary (start)
[Part content]
------WebKitFormBoundary (next Part)
[Part content]
------WebKitFormBoundary-- (end, note the two dashes)

Filename Handling

Special Characters

Problem: Filename contains non-ASCII characters

Solution 1: RFC 2231 encoding
Content-Disposition: form-data; name="file";
filename*=UTF-8''%E6%96%87%E4%BB%B6.txt

Solution 2: URL encoding (not recommended)
filename="%E6%96%87%E4%BB%B6.txt"

Solution 3: ASCII only (most compatible)
filename="file.txt"

Security Considerations

⚠️ Dangerous filenames:
filename="../../../etc/passwd" (path traversal)
filename="evil.php" (executable file)
filename="file<script>.txt" (injection attack)

✅ Safe handling:
1. Remove path separators (/ \)
2. Whitelist extensions
3. Rename to UUID
4. Limit length (e.g., 255 characters)
5. Virus scan

Example:
filename = secure_filename(original_filename)
# "../../evil.php" → "evil.php" → "f81d4fae.dat"

Large File Upload

Stream Processing

// Node.js (Busboy)
const Busboy = require('busboy');

app.post('/upload', (req, res) => {
const busboy = Busboy({ headers: req.headers });

busboy.on('file', (name, file, info) => {
const { filename, encoding, mimeType } = info;
const saveTo = `uploads/${filename}`;

// Stream write, no memory usage
file.pipe(fs.createWriteStream(saveTo));
});

busboy.on('finish', () => {
res.send('Upload complete');
});

req.pipe(busboy);
});

Chunked Upload

// Client: Upload large file in chunks
const CHUNK_SIZE = 1024 * 1024; // 1MB
const file = fileInput.files[0];
const chunks = Math.ceil(file.size / CHUNK_SIZE);

for (let i = 0; i < chunks; i++) {
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);

const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkIndex', i);
formData.append('totalChunks', chunks);
formData.append('filename', file.name);

await fetch('/upload-chunk', {
method: 'POST',
body: formData
});
}

Debugging Tips

Chrome DevTools

1. Network tab
2. Select upload request
3. View Request Payload
- See complete multipart/form-data structure
- boundary delimiter
- Headers and content of each Part

curl Testing

# Simple upload
curl -F "username=Alice" \
-F "[email protected]" \
https://example.com/upload

# Specify Content-Type
curl -F "[email protected];type=application/pdf" \
https://example.com/upload

# Multiple files
curl -F "[email protected]" \
-F "[email protected]" \
https://example.com/upload

# Verbose output
curl -v -F "[email protected]" https://example.com/upload

Common Questions

Q: When to use multipart/form-data?

Use cases:
✅ File uploads
✅ Binary data transfer
✅ Mixed text and files

Don't use for:
❌ Plain text forms (use application/x-www-form-urlencoded)
❌ JSON APIs (use application/json)
❌ Many small fields (inefficient)

Q: Performance impact?

Pros:
+ No encoding needed for binary data

Cons:
- Boundary adds overhead
- Extra headers per Part
- More complex parsing than JSON

Recommendation:
- Use when uploading files
- Consider JSON for data only

Q: Maximum file size?

Limits from:
1. Server configuration
- Nginx: client_max_body_size 100M;
- Apache: LimitRequestBody 104857600

2. Language/framework
- PHP: upload_max_filesize = 100M
- Node.js: multer({ limits: { fileSize: 100MB } })

3. Browser
- Usually no limit
- Very large files may cause memory issues

multipart/form-data is the standard for web file uploads.

Key Points:

  • 📋 Form Encoding: HTML form file uploads
  • 📦 Binary Support: No Base64 encoding needed
  • 🔀 Mixed Data: Text and files together
  • 🎯 Efficient Transfer: Large file friendly

Quick Usage:

<form enctype="multipart/form-data">
<input type="file" name="file">
</form>
const formData = new FormData();
formData.append('file', file);
fetch('/upload', { method: 'POST', body: formData });

Security Tips:

  • Validate file types
  • Limit file size
  • Sanitize filenames
  • Virus scan

Related RFCs:

  • RFC 2046 - MIME Media Types
  • RFC 2231 - Parameter Value Encoding
  • RFC 7233 - Range Requests (resumable uploads)