How to upload files in a Next.js form
By Flavio Copes
Learn how to handle file uploads in a Next.js form by parsing multipart data with next-connect and multiparty middleware and disabling the default bodyParser.
To upload files from a Next.js form you need two things: a form that sends multipart/form-data, and an API route that can parse it. The second part is the tricky one, because Next.js API routes (in the pages/api directory) don’t parse multipart bodies out of the box.
Here’s how I ran into this. I had a form in a Next.js page:
<form method="post" action="/api/new" enctype="multipart/form-data">...</form>
which called an API endpoint.
Inside this form I had a file input control:
<input name="logo" type="file" />
Now in the API route, I wasn’t able to get this file.
Why doesn’t the API route see the file?
Next.js runs a body parser on every API request by default. It handles JSON and URL-encoded data, but not multipart/form-data, which is the encoding browsers use to send files. So the file never shows up in req.body.
I tried various solutions because some didn’t play well with uploading files AND sending multiple checkboxes for the same attribute. With some solutions I got the file, but the rest of the form didn’t work as expected.
What worked: disable the default body parser, and let a dedicated multipart parser read the raw request stream.
I had to install 2 packages:
npm install next-connect multiparty
multiparty parses the multipart stream. It writes each uploaded file to a temporary folder on disk, and gives you the form fields and the files metadata. next-connect lets us plug it in as middleware.
I created a middleware folder in the Next.js project root, and inside it this file:
middleware/middleware.js
import nextConnect from 'next-connect'
import multiparty from 'multiparty'
const middleware = nextConnect()
middleware.use(async (req, res, next) => {
const form = new multiparty.Form()
await form.parse(req, function (err, fields, files) {
req.body = fields
req.files = files
next()
})
})
export default middleware
Then I changed the API route from the usual structure:
export default async function handler(req, res) {
//...
}
to:
import middleware from 'middleware/middleware'
import nextConnect from 'next-connect'
const handler = nextConnect()
handler.use(middleware)
handler.post(async (req, res) => {
console.log(req.body)
console.log(req.files)
//...
})
export const config = {
api: {
bodyParser: false
}
}
export default handler
I restarted Next.js and I was able to get my files data in the API route.
What you get back
Notice that multiparty wraps every value in an array, even when there’s a single one. A text field called title is at req.body.title[0], and my logo file is at:
const logo = req.files.logo[0]
logo.originalFilename //'logo.png'
logo.size //34532
logo.path //'/tmp/tmp-1234abcd.png'
The path property points to the temporary file multiparty saved. From there you can read it, upload it to storage, or copy it where you need it:
import fs from 'fs/promises'
await fs.copyFile(logo.path, `./uploads/${logo.originalFilename}`)
The pitfall: forgetting to disable bodyParser
The config export with bodyParser: false is not optional. If you leave it out, Next.js consumes the request body before multiparty gets a chance to read the stream, and you end up with empty fields and no files.
Also, the export must live in the API route file itself. Putting it in the middleware file does nothing.
Related posts about next: