Defining File Uploads: multipart/form-data vs Base64 Bodies
Quick answer: For file uploads, multipart/form-data is the default correct choice because it streams binary bytes alongside other fields without inflating size, while a Base64 body is a workaround for channels that cannot carry raw binary and costs roughly a third extra size plus an encode and decode step. In OpenAPI you express the multipart case with a requestBody of type object and a property of type string, format binary, and you reserve the Base64 shape for the specific transports that need it, such as a JSON-only message queue. The decision should be made from the transport and size constraints, not from whichever encoding was quickest to implement. If you default to Base64 for a normal HTTP upload, you pay the overhead on every request and make large files and streaming effectively impossible, while clients that expect a standard multipart form cannot reuse their normal upload logic.
Uploads are a place where the API description has to be precise because the generated clients and the server's parser depend on the exact request shape. A multipart request divides the body into parts, each with its own headers, so a binary file can travel as raw bytes while ordinary fields are sent in the same body. A Base64 approach encodes the bytes into ASCII text, usually inside a JSON object, which makes the payload safe for a text-only channel but is a representation rather than a transfer of the file itself. Both are valid in the right context, and the schema should make the chosen one unambiguous rather than leaving clients to guess which encoding the endpoint expects.
Comparing the two encodings
The trade-offs are concrete and tend to point the same way for direct HTTP uploads.
| Factor | multipart/form-data | Base64 in JSON |
|---|---|---|
| Size overhead | Small part framing | About 33% larger |
| Streaming | Yes | Generally buffered |
| Multiple fields | Native parts | Nested in JSON |
| Transport fit | HTTP uploads | JSON-only channels |
| Client support | Browser FormData | Manual encode |
The streaming row is decisive for large files: a multipart upload can be processed as it arrives, whereas a Base64 JSON body is typically buffered in full before it can be decoded, which raises memory use and blocks progress reporting.
Define a multipart upload in OpenAPI
The request body is an object whose properties include the file as a binary string, and the media type is multipart/form-data. This lets generators produce a client that sends a real form.
paths:
/uploads:
post:
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file, category]
properties:
file:
type: string
format: binary
category:
type: string
Adding maxLength or a documented size limit on the upload is worth doing because the format alone does not bound how large a request can be. Pairing the schema with a stated maximum and the expected error response for oversized files gives clients a complete contract rather than an open-ended endpoint.
When Base64 is genuinely justified
For a JSON-only downstream, such as a message queue or a legacy system that accepts a single JSON envelope, describe the file as a string with format byte and keep the Base64 encoding explicit so the contract says exactly what is being sent.
content:
application/json:
schema:
type: object
properties:
fileName: { type: string }
fileData:
type: string
format: byte # Base64-encoded content
Keeping these two shapes separate rather than overloading one endpoint with both encodings matters because ambiguity here produces clients that send the wrong content type and servers that fail to parse the body. If a system genuinely needs both, expose them as distinct, clearly named operations so the request shape is unambiguous on each.
Validate the real round trip
Generated clients should be tested with an actual binary file, not just a tiny text fixture, to confirm the bytes survive the round trip unchanged and that the server reports correct content type and size. The contract should declare the accepted media types, size limit, and the error response for a rejected upload, so the multipart or Base64 behavior is documented rather than inferred from the implementation. A checksum assertion on the decoded file is a cheap way to catch encoding corruption that status codes alone would miss.
The practical rule is to start from multipart/form-data for any ordinary HTTP upload and reach for Base64 only when the transport genuinely cannot carry binary. Describe the binary property with format binary for multipart and format byte for Base64, declare size limits, and test the round trip with a real file. Choosing Base64 by default inflates every request, blocks streaming, and forces clients into manual encoding, costs that are avoidable on a standard HTTP API.
Frequently asked questions
Why is multipart usually preferred over Base64?
It carries raw binary bytes with small framing overhead, supports streaming, and works natively with browser FormData, whereas Base64 adds about a third to the size and usually buffers the whole payload.
How do I mark a file field in OpenAPI for multipart?
Use a string property with format binary inside an object schema under the multipart/form-data media type, which generators turn into a real form upload.
When is Base64 the right choice?
When the transport only accepts JSON text, such as a message queue or a legacy JSON envelope, then use format byte and keep the encoding explicit in the contract.
Should one endpoint accept both encodings?
Avoid overloading one endpoint, because ambiguous content types cause parse failures. If both are truly needed, expose separate operations with clear names and schemas.
How do I confirm an upload is not corrupted?
Test with a real binary file, compare a checksum of the decoded bytes against the source, and verify the server records the correct content type and size.
This is part of the The OpenAPI Guide — start there for the full picture of designing robust APIs.