How FormData Handles Multipart Uploads in JavaScript
The JavaScript FormData interface provides a streamlined
way to construct a set of key-value pairs representing form fields and
their values, which can be easily transmitted using HTTP requests. This
article explains how FormData captures data, structures
text and binary files, automatically generates multipart boundaries, and
prepares the payload for multipart/form-data encoding
without requiring manual string concatenation or header management.
Instantiating and Populating Data
A FormData object can be instantiated empty or populated
directly from an existing HTML <form> element:
// Empty instance
const formData = new FormData();
// From an HTML form element
const formElement = document.querySelector('form');
const populatedData = new FormData(formElement);You populate data using two primary methods: *
append(name, value, [filename]): Adds a new value to an
existing key or creates the key if it does not exist. *
set(name, value, [filename]): Overwrites any existing value
for the given key with the new value.
Handling Text vs. Binary Payloads
When appending values, FormData differentiates between
text strings and binary objects (Blob or
File):
- Text Fields: If the value is not a
BloborFile, the browser converts it to a string. - Binary Files: When passing a
BloborFile, an optional third argument specifies the filename. If omitted for aBlob, it defaults to"blob".
formData.append('username', 'alex_dev');
formData.append('profilePicture', fileInput.files[0], 'avatar.png');Constructing the Multipart Payload Structure
Under the hood, FormData serializes entries into the
multipart/form-data format (defined by RFC 7578). The
payload is composed of multiple distinct parts separated by a unique
delimiter called a boundary.
For each key-value pair, the browser generates: * Boundary
Delimiter: A unique string guaranteed not to occur within the
data payloads. * Content-Disposition Header: Identifies
the field name and, for files, the filename. * Content-Type
Header: Added automatically for binary data based on the
Blob.type or file extension (defaults to
application/octet-stream if unknown). Plain text fields
typically omit this or default to text/plain. *
Payload Body: The raw text or binary stream followed by
a carriage return and line feed (\r\n).
An assembled payload internally resembles the following structure:
--WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="username"
alex_dev
--WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="profilePicture"; filename="avatar.png"
Content-Type: image/png
<binary data>
--WebKitFormBoundary7MA4YWxkTrZu0gW--
Transmission with Fetch and XMLHttpRequest
When passing a FormData instance as the body of a
fetch request or XMLHttpRequest, the browser
automatically computes the correct Content-Type header
along with the generated boundary parameter:
fetch('/api/upload', {
method: 'POST',
body: formData
});The resulting request header looks like:
Content-Type: multipart/form-data; boundary=WebKitFormBoundary7MA4YWxkTrZu0gW
Manually setting the Content-Type header when sending
FormData must be avoided, as doing so removes the boundary
parameter and prevents the server from correctly parsing the individual
key-value pairs.