clock menu more-arrow no yes

Dev tip: How to dynamically set content-type headers for S3 presigned urls with a native fetch request

Jared Fanning

Among other things, the Vox Product team makes apps for building custom advertisements. Recently I was building out a new feature for one of our advertising apps which involves uploading an image asset. My coworker Drew had designed our app to output a S3 pre-signed url that allows an image upload to a specific S3 bucket. My task was to use the Fetch API to POST that image to the bucket. To restrict uploads to images, we are using the “content_type_starts_with” option with the value “image/”:

Though we were originally following these instructions from Heroku, I refactored the code to be vanilla JS, as we didn’t need the jQuery libs referenced in that tutorial. My first pass looked like this:

The code above:

  1. Creates a “FormData” object
  2. Adds all the AWS data to it
  3. Adds the file object to the FormData when a file is added to the form input
  4. Sets the content-type based on the file type
  5. Triggers a POST request via Fetch to upload the file to S3

This code, unlike the Heroku jQuery version, returns a 403:forbidden error when I used it to upload an image. I spent hours trying to figure out why. The jQuery version used XMLHttpRequest, rather than Fetch; I first thought that might have something to do with the error. With console.logs and debuggers everywhere, I pulled up each version in a different tab and compared the form headers of each request to see if there was anything different. However, everything seemed the same.

screenshot of slack interface, showing my all caps text “HTTP REQUESTS ARE VERY CONFUSING” and my coworker Brian responding LOL.
My coworker Brian empathizing with my plight

Finally, in our Slack channel dedicated to screaming, I typed “HTTP REQUESTS ARE VERY CONFUSING,” which prompted my coworkers Josh and Emily to offer to pair on a video call to see if we could jointly solve the problem.

Together we discovered a solution that is infuriatingly simple. The content-type needs to be set first in the FormData — before the file is appended — so that the server can confirm the file meets the defined rules. The prior code only needed a single change: I had to move line 14 before line 13. Once I made that change, uploads were all 201:success.

Here’s the working code we put in place:

If ever you encounter this issue, I only hope you find this blog post before you reach the point of screaming!