Multipart Form Data

By Phil Sturgeon
Last update on August 06, 2024

Multipart form data is a content type used for HTTP requests to send a combination of text and file data in a single request, allowing for one or more uploaded files to be sent with other form fields.

In the context of an API, using multipart form data enables clients to send files to the server as part of a single API request, avoiding the need for metadata to be uploaded as one request then linking it back to the file somehow. This is handy when building resources that might have an avatar or a cover image, or sending a PDF that needs a name, description, and list of recipients.

POST /upload HTTP/2
Host: api.example.com
Content-Type: multipart/form-data; boundary=--------------------------1234567890

----------------------------1234567890
Content-Disposition: form-data; name="metadata"
Content-Type: application/json

{
  "name": "My PDF Document",
  "recipients": [
    "andy@example.com",
    "sarah@example.com"
  ]
}
----------------------------1234567890
Content-Disposition: form-data; name="description"

This is a long form text field which can also be sent if JSON is not your thing.
----------------------------1234567890
Content-Disposition: form-data; name="file"; filename="document.pdf"
Content-Type: application/octet-stream

[base64 encoded PDF file data]
----------------------------1234567890--

OpenAPI supports APIs using multipart form data in requestBody, by specifying the multipart/form-data content type.

paths:
  /upload:
    post:
      summary: Upload a file
      description: Endpoint to upload a file
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              # ...
                

Within the schema property of the requestBody, you must define the structure of the multipart form data. When boundaries are used to separate segments of the content being transferred, you will need to describe the Content-Types, or you can leave it with the sensible default values:

  requestBody:
    required: true
    content:
      multipart/form-data:
        schema:
          type: object
          properties:
            # default Content-Type for objects is `application/json`
            metadata: 
              type: object
              properties: 
                name: 
                  type: string
                recipients:
                  type: array
                  items: 
                    type: string
            # Content-Type for application is `text/plain`
            description:
              type: string
            # Content-Type for a string with a contentEncoding set is `application/octet-stream`
            file:
              type: string
              contentEncoding: base64

This OpenAPI declares the schema to match the previous HTTP request example, documenting a JSON object with name and recipient array, a plain text description. No content media types had to be defined for either of those segments because OpenAPI will guess them, and usually guess right. The object is assumed to be JSON. The string with no other information is assumed to be plain text.

Other sorts of segments can be a bit tricker, and need a bit of configuration.

Content Encoding #

By default if a string has a contentEncoding set, the default will be application/octet-stream, which will accept any sort of file. This API would like a PDF but could also accept images and other files, so that’s often just fine, and the server can reject invalid files later on.

The contentEncoding is there to help define what form the data is in. Some file uploads are base64 encoded, so that can be described with contentEncoding: base64. The contentEncoding keyword supports all encodings defined in RFC4648, including "base64" and "base64url", as well as "quoted-printable" from RFC2045 Section 6.7.

Content Media Type #

When you need to override the default contentMediaType assumptions for any of the multipart segments, you can update the schema for that segment to be as specific as you want.

  content:
    multipart/form-data:
      schema:
        type: object
        properties:
          # One specific file type, but still text
          csvSpecifically:
            type: string
            contentMediaType: text/csv
          # Either PNG or JPEG, both encoded as base64
          pngOrJpegImage:
            type: string
            contentMediaType: image/png, image/jpeg
            contentEncoding: base64
          # Any image is fine, but it'll be base64
          anyImage:
            type: string
            contentMediaType: image/*
            contentEncoding: base64

You can use a single mime type, a comma separated list, or use a wildcard (*) to support multiple.

Encoding Object #

The contentEncoding and contentMediaType keywords should handle most scenarios, but if you need to get even more involved with describing each of the multipart segments, including even the headers on that segment, take a look at the encoding object.

requestBody:
  content:
    multipart/form-data:
      schema:
        type: object
        properties:
          id:
            # default is text/plain
            type: string
            format: uuid
          address:
            # default is application/json
            type: object
            properties: {}
          historyMetadata:
            # need to declare XML format!
            description: metadata in XML format
            type: object
            properties: {}
          profileImage: {}
      encoding:
        historyMetadata:
          # require XML Content-Type in utf-8 encoding
          contentType: application/xml; charset=utf-8
        profileImage:
          # only accept png/jpeg
          contentType: image/png, image/jpeg
          headers:
            X-Rate-Limit-Limit:
              description: The number of allowed requests in the current period
              schema:
                type: integer

Take a look at the OpenAPI v3.1 Specification to learn more about the Encoding Object, and see how to handle custom headers and even “styles” and “explode” to handle complex data expressed in a string form.

Multipart form data is only one way to handle file uploads, so read the File Uploads guide to see other ways to do it.

Handling Error Formats