Skip to content

Handling file uploads in forms using Express

How to manage storing and handling files uploaded via forms, in Express

This is an example of an HTML form that allows a user to upload a file:

<form method="POST" action="/submit-form" enctype="multipart/form-data">
  <input type="file" name="document" />
  <input type="submit" />
</form>

Don’t forget to add enctype="multipart/form-data" to the form, or files won’t be uploaded

When the user press the submit button, the browser will automatically make a POST request to the /submit-form URL on the same origin of the page. The browser sends the data contained, not encoded as as a normal form application/x-www-form-urlencoded , but as multipart/form-data.

Server-side, handling multipart data can be tricky and error prone, so we are going to use a utility library called formidable. Here’s the GitHub repo, it has over 4000 stars and is well-maintained.

You can install it using:

npm install formidable

Then include it in your Node.js file:

const express = require('express')
const app = express()
const formidable = require('formidable')

Now, in the POST endpoint on the /submit-form route, we instantiate a new Formidable form using formidable.IncomingForm():

app.post('/submit-form', (req, res) => {
  new formidable.IncomingForm()
})

After doing so, we need to be able to parse the form. We can do so synchronously by providing a callback, which means all files are processed, and once formidable is done, it makes them available:

app.post('/submit-form', (req, res) => {
  new formidable.IncomingForm().parse(req, (err, fields, files) => {
    if (err) {
      console.error('Error', err)
      throw err
    }
    console.log('Fields', fields)
    console.log('Files', files)
    for (const file of Object.entries(files)) {
      console.log(file)
    }
  })
})

Or, you can use events instead of a callback. For example, to be notified when each file is parsed, or other events such as completion of file processing, receiving a non-file field, or if an error occurred:

app.post('/submit-form', (req, res) => {
  new formidable.IncomingForm().parse(req)
    .on('field', (name, field) => {
      console.log('Field', name, field)
    })
    .on('file', (name, file) => {
      console.log('Uploaded file', name, file)
    })
    .on('aborted', () => {
      console.error('Request aborted by the user')
    })
    .on('error', (err) => {
      console.error('Error', err)
      throw err
    })
    .on('end', () => {
      res.end()
    })
})

Whichever way you choose, you’ll get one or more Formidable.File objects, which give you information about the file uploaded. These are some of the methods you can call:

The path defaults to the temporary folder and can be modified if you listen for the fileBegin event:

app.post('/submit-form', (req, res) => {
  new formidable.IncomingForm().parse(req)
    .on('fileBegin', (name, file) => {
        file.path = __dirname + '/uploads/' + file.name
    })
    .on('file', (name, file) => {
      console.log('Uploaded file', name, file)
    })
    //...
})
→ Get my Express.js Handbook

I wrote 17 books to help you become a better developer, download them all at $0 cost by joining my newsletter

JOIN MY CODING BOOTCAMP, an amazing cohort course that will be a huge step up in your coding career - covering React, Next.js - next edition February 2025

Bootcamp 2025

Join the waiting list