How to send and read multipart/form-data with Logic Apps?📄

Table of Contents

Background

The support for working with json, xml, or text files is great in Logic Apps. However, sometimes there may be a need to send a customized message or a request to a service, and those can be a bit more challenging. In this guide, we will specifically look into the content type known as multipart/form-data!

What is the Content-Type HTTP header?🏷️

The Content-Type HTTP header describes the format of the payload in both HTTP requests and responses, allowing the receiving side to correctly decode or parse the data. There are different content types, with application/json and application/xml being the most common in REST APIs. It is important not to confuse Content-Type with Content-Encoding, which refers to how the resource is encoded.

What is multipart/form-data?

Let’s continue with the content type multipart/form-data. You will likely encounter this when working with HTML forms. For example, a user may need to submit their first and last name while also attaching a file. There are other implementations where the multipart/form-data content type is used, but this should give you an idea of where you might encounter it.

Let’s say that we have an HTML form that will submit the following fields to our backend service:

  • Age
  • First name
  • and Last name

How does it look and work? Below is an example of a raw HTTP post request with multipart/form-data using the Postman client:

POST /anything HTTP/1.1
Host: httpbin.org
Content-Type: multipart/form-data; boundary=--------------------------671747156319508458970968
 
----------------------------671747156319508458970968
Content-Disposition: form-data; name="Age"
30
----------------------------671747156319508458970968
Content-Disposition: form-data; name="FirstName"
Foo
----------------------------671747156319508458970968
Content-Disposition: form-data; name="LastName"
Bar
----------------------------671747156319508458970968--

One thing that stands out in the payload body, is that the content is sent as blocks of data. Each block is separated by a boundary value, which separates each segment. Within each block, the Content-Disposition header is declared, containing information and metadata about the form data block, for example field name.

It is also important to mention that the boundary value is part of the specification and must be implemented by the sending party. However, in most cases, the value is auto-generated by tools and libraries, rather than being manually defined by the user.

Multipart/form-data with Logic Apps

Read data 👀

Start by creating a simple workflow using the Request trigger connector. If we post the same request described in the section What is multipart/form-data? but now we instead call this workflow and head into to the trigger output, you will have something similar to this:

// Some fields in the output has been removed for readability. 
{
    "headers": {
        "Content-Type": "multipart/form-data; boundary=--------------------------493073486649885477988289"
    },
    "body": {
        "$content-type": "multipart/form-data; boundary=--------------------------493073486649885477988289",
        "$content": "LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTQ5MzA3MzQ4NjY0OTg4NTQ3Nzk4ODI4OQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IkZpcnN0TmFtZSIKCkZvbwotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tNDkzMDczNDg2NjQ5ODg1NDc3OTg4Mjg5CkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iTGFzdE5hbWUiCgpCYXIKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTQ5MzA3MzQ4NjY0OTg4NTQ3Nzk4ODI4OQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9IkFnZSIKCjMwCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS00OTMwNzM0ODY2NDk4ODU0Nzc5ODgyODktLQ==",
        "$multipart": [
            {
                "headers": {
                    "Content-Disposition": "form-data; name=\"FirstName\"",
                    "Content-Length": "3"
                },
                "body": {
                    "$content-type": "application/octet-stream",
                    "$content": "Rm9v"
                }
            },
            {
                "headers": {
                    "Content-Disposition": "form-data; name=\"LastName\"",
                    "Content-Length": "3"
                },
                "body": {
                    "$content-type": "application/octet-stream",
                    "$content": "QmFy"
                }
            },
            {
                "headers": {
                    "Content-Disposition": "form-data; name=\"Age\"",
                    "Content-Length": "2"
                },
                "body": {
                    "$content-type": "application/octet-stream",
                    "$content": "MzA="
                }
            }
        ]
    }
}

At first glance, this might look a bit cryptic, and it does not exactly remind you of the request that was sent from the Postman client. This is because Logic Apps has converted the payload into a json format and the blocks in the multipart-form have been converted into an array, which can be found under the $multipart property.

Each object in the multipart array contains a header and a body. The header provides the metadata such as field name, which is useful when you want to filter out on specific fields. The body contains the data and in this case is represented as application/octet-stream, which simply means that Logic Apps is keeping the content “intact”.

It is worth mentioning that the value of the $content property is the raw HTTP body and can be decoded using base64.

There are two ways to read these kinds of payloads in Logic Apps:

  • you can either loop over the $multipart array to extract the data.
  • or you can use one of the expression, multipartBody or triggerMultipartBody, which require an index as input to get the data.

Using the For each action and looping over the $multipart property: workflow_read_by_for_each

And an example of using the expression triggerMultipartBody and selecting the index 0 in the multipart array: workflow_read_by_index

Send data ✉️

In order to send a multipart/form-data payload we need to use the HTTP connector and compose a similar body that was seen in the Read the data section, a json body with the $content-type and $multipart array property. Create another workflow and copy in the code below and paste into HTTP connector body:

{
  "$content-type": "multipart/form-data",
  "$multipart": [
    {
      "headers": {
        "Content-Disposition": "form-data; name=\"FirstName\"",
      },
      "body": "Foo"
    },
    {
      "headers": {
        "Content-Disposition": "form-data; name=\"LastName\"",
      },
      "body": "Bar"
    },
    {
      "headers": {
        "Content-Disposition": "form-data; name=\"Age\"",
      },
      "body": "30"
    }
  ]
}

Notice that we did not set the boundary value since this gets auto generated by HTTP connector. You can test this workflow by calling the former workflow to make sure it works as intended.

Below is the final code configuration for sending a multipart/form-data message: workflow_send

Reflections

Once you figure out how the HTTP connector works with its syntax, it works quite well. You will probably need to do some additional quirks and fixes to make sure the data is sent correctly.