FastAPI returns "Error 422: Unprocessable entity" when I send multipart form data with JavaScript Fetch API

Question:

I have some issue with using Fetch API JavaScript method when sending some simple formData like so:

function register() {
  var formData = new FormData();
  var textInputName = document.getElementById('textInputName');
  var sexButtonActive = document.querySelector('#buttonsMW > .btn.active');
  var imagesInput = document.getElementById('imagesInput');

  formData.append('name', textInputName.value);
  if (sexButtonActive != null){
    formData.append('sex', sexButtonActive.html())
  } else {
    formData.append('sex', "");
  }
  formData.append('images', imagesInput.files[0]);

  fetch('/user/register', {
    method: 'POST',
    data: formData,
  })
  .then(response => response.json());
}
document.querySelector("form").addEventListener("submit", register);

And on the server side (FastAPI):

@app.post("/user/register", status_code=201)
def register_user(name: str = Form(...), sex: str = Form(...), images: List[UploadFile] = Form(...)):
try:
    print(name)
    print(sex)
    print(images)
    return "OK"
except Exception as err:
    print(err)
    print(traceback.format_exc())
    return "Error"

After clicking on the submit button I get Error 422: Unprocessable entity. So, if I’m trying to add header Content-Type: multipart/form-data, it also doesn’t help cause I get another Error 400: Bad Request. I want to understand what I am doing wrong, and how to process formData without such errors?

Asked By: Egor Zamotaev

||

Answers:

The 422 response body will contain an error message about which field(s) is missing or doesn’t match the expected format. Since you haven’t provided that (please do so), my guess is that the error is triggered due to how you defined the images parameter in your endpoint. Since images is expected to be a List of File(s), you should instead define it using the File type instead of Form. For example:

images: List[UploadFile] = File(...)
                           ^^^^    

When using UploadFile, you don’t have to use File() in the default value of the parameter. Hence, the below should also work:

images: List[UploadFile]

Additionally, in the frontend, make sure to use the body (not data) parameter in the fetch() function to pass the FormData object (see example in MDN Web Docs). For instance:

fetch('/user/register', {
      method: 'POST',
      body: formData,
   })
   .then(res => {...

Please have a look at this answer, as well as this answer, which provide working examples on how to upload multiple files and form data to a FastAPI backend, using Fetch API in the frontend.

As for manually specifying the Content-Type when sending multipart/form-data, you don’t have to (and shouldn’t) do that, but rather let the browser set the Content-Type—please take a look at this answer for more details.

Answered By: Chris

So, I found that I has error in this part of code:

formData.append('images', imagesInput.files[0]);

Right way to upload multiple files is:

for (const image of imagesInput.files) {
    formData.append('images', image);
}

Also, we should use File in FastAPI method arguments images: List[UploadFile] = File(...) (instead of Form) and change data to body in JS method. It’s not an error, cause after method called, we get right type of data, for example:

Name: Bob
Sex: Man
Images: [<starlette.datastructures.UploadFile object at 0x7fe07abf04f0>]
Answered By: Egor Zamotaev