Multer: Multipart Form Handling in Express, and Where It Stops
Node.js middleware for handling `multipart/form-data`.
At a glance
- What is it?
- Multer parses multipart/form-data requests in Express and puts files on req.file or req.files. It is a parser with two built-in storage engines, not an upload pipeline, and the README is explicit about what it will not process.
- Who is it for?
- Adopt Multer when your Express routes need multipart/form-data parsed and you are content to own validation, file naming and storage cleanup yourself; it is the wrong tool for JSON-only APIs, for non-multipart forms, and for teams that want virus scanning, resizing or cloud uploads handled inside the middleware.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 16 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What Multer parses that express.json and express.urlencoded will not
Multer is a Node.js middleware for handling multipart/form-data, which the package description calls out as its entire scope. That matters because Express ships body parsers for JSON and URL-encoded payloads, and neither touches multipart bodies. A form with enctype="multipart/form-data" arrives as a stream of parts, and without a parser the request body is simply not populated. Multer fills that gap: it adds a body object and a file or files object to the request, so text fields land in req.body and uploaded files land in req.file or req.files.
The README is blunt about the boundary. Multer will not process any form which is not multipart. If your client posts application/json to a route guarded by upload.single(), the middleware has nothing to do. That is a deliberate constraint rather than a bug, and it is the first thing to check when an upload silently produces an empty req.file.
Who it is for: Express applications that accept browser form uploads, whether a single avatar field, an array of photos, or a mixed form with several named file fields plus text inputs. The README also covers the text-only case through upload.none(), which parses a multipart form that contains no files at all. That method exists precisely because a multipart form with only text fields still needs a parser, and express.urlencoded will not read it.
How Multer moves bytes: busboy, two storage engines, and the request object
Multer is written on top of busboy, and the dependency list in package.json confirms it: busboy ^1.6.0, alongside append-field and type-is. The README describes the arrangement as being built on busboy for maximum efficiency. The parser does the part-splitting; Multer maps the resulting parts onto the request.
The data flow has a fork in it, and the fork is the storage option. With dest set, files go to disk and each file object carries destination, filename and path. With MemoryStorage, files never touch disk and each file object carries a buffer instead. The README's file information table makes the split explicit: destination, filename and path are annotated DiskStorage, while buffer is annotated MemoryStorage. If you omit the options object entirely, the files are kept in memory and never written to disk. That default is easy to miss and it is the difference between a working upload and a process that grows with every request.
By default, Multer renames files to avoid naming conflicts, and the README states the renaming function can be customized. The options table exposes the controls you would expect for a parser at this layer: limits for uploaded data, fileFilter to control which files are accepted, defCharset and defParamCharset for character set defaults, fileHwm and highWaterMark for stream buffering, and preservePath, which keeps the full client-supplied path in file.originalname instead of just the base name.
One detail in the README deserves attention because it is a security-relevant footgun rather than a feature. When preservePath is enabled, Multer passes the incoming filename through with any path segments the client provided. The README states plainly that this does not change the destination folder, does not create directories, and does not sanitize the path. It also states that file.originalname is always client-supplied and should be treated as untrusted, and that with preservePath it additionally contains the path segments the client sent. If you write a custom filename function or a storage engine, that value reaches your code unfiltered. Normalize or validate it yourself.
Installing Multer and wiring a first working upload route
Installation is a single npm command, and the README gives it without ceremony:
npm install multerThe package requires Node >= 10.16.0 according to the engines field in package.json. After install, the smallest useful setup passes a dest so files are written to disk rather than held in memory. The README's basic example looks like this:
const express = require('express')
const multer = require('multer')
const upload = multer({ dest: 'uploads/' })
const app = express()
app.post('/profile', upload.single('avatar'), function (req, res, next) {
// req.file is the `avatar` file
// req.body will hold the text fields, if there were any
})The argument to upload.single is the field name, and it must match the name attribute in the HTML form. The README warns about exactly this mismatch: if the fields are not the same in the HTML form and on your server, your upload will fail. The form side needs the encoding declared, which the README shows as enctype="multipart/form-data" on the form element and a matching name on the input.
<form action="/profile" method="post" enctype="multipart/form-data">
<input type="file" name="avatar" />
</form>For multiple files under one field name, upload.array takes the field name and an optional maxCount, and the results arrive in req.files as an array. For several distinct file fields, upload.fields takes an array of objects with name and maxCount keys, and req.files becomes an object keyed by field name, where each value is an array. The README's example uses { name: 'avatar', maxCount: 1 } and { name: 'gallery', maxCount: 8 }, and notes that req.files['avatar'][0] is a File while req.files['gallery'] is an Array. One subtlety worth knowing: files skipped by fileFilter do not count towards maxCount. If you rely on maxCount as a hard ceiling on accepted files, a filter that rejects some of them changes the arithmetic. Use limits.files for the count constraint instead.
Where Multer stops: memory defaults, missing validation, and the wrong use cases
The most consequential limitation is the default. Omit the options object and files are kept in memory and never written to disk. For a small avatar that is fine; for a route that accepts large uploads it is a straightforward way to exhaust process memory, because every concurrent request holds its full payload as a buffer. The README documents the behaviour rather than warning about it, so the burden of choosing MemoryStorage deliberately falls on the reader.
Multer also does not validate file contents. fileFilter lets you control which files are accepted, but that is a decision function you write, and the mimetype value it inspects comes from the client. The README's own framing of file.originalname as always client-supplied and untrusted applies equally to mimetype. Nothing in the package scans for malware, checks magic bytes, or resizes images.
Nor does Multer clean up after itself. On DiskStorage it writes files to the destination folder and returns the path; deleting rejected uploads, expiring stale files, and handling the case where a later validation step fails after the file is already on disk are all application concerns. A route that saves the file and then rejects the record leaves an orphan.
The wrong-tool cases follow from that. If your API is JSON-only, Multer is inert, because it will not process any form which is not multipart. If you need uploads to land in object storage with transformations, the README points at third-party storage engines rather than claiming to do it, and the README does not document what those engines guarantee. And if you need the parser to enforce a content policy, it will not; it enforces shape and size limits, not trust.
Alternatives: busboy directly, and third-party storage engines instead of DiskStorage
The most direct alternative is busboy itself, which Multer wraps. Using busboy directly means you handle the multipart stream yourself: you subscribe to part events, decide per part whether it is a file or a field, and write or buffer it as you see fit. The difference in approach is that Multer hands you a finished req.file or req.files after parsing and storage complete, while busboy hands you the stream and leaves both decisions to you. If your requirements are unusual, for example streaming a part straight to a remote destination without ever materializing it locally, going one layer down is the honest answer. If your requirement is the standard Express case, Multer is the shorter path, and the README's framing of busboy as the efficiency layer suggests the wrapper is not the expensive part.
The other axis of choice is storage. Multer ships DiskStorage and MemoryStorage, and the README states that more engines are available from third parties. That is where the real fork sits for production: DiskStorage gives you destination, filename and path on a local filesystem, MemoryStorage gives you a buffer, and anything else, including object storage, comes from outside the package. The repository also contains a StorageEngine.md file at the top level, which is where a custom engine's contract would be described; the README itself does not restate it, so read that file before writing one rather than inferring the interface from the two built-ins.
There is also a version split worth noting. The recent releases list includes v2.4.0 and v2.3.0 on the 2.x line, plus a v3.0.0-alpha.2 published earlier. The alpha is not the current release. If you are pinning versions, the stable line is 2.x.
Maintenance, licence and the cost of staying current
The repository is not archived, and the last push was on 2026-09-14, the same date as the v2.4.0 release. The release cadence visible in the release list shows v2.3.0 on 2026-08-28 and v2.4.0 on 2026-09-14, so the 2.x line has seen recent activity. A v3.0.0-alpha.2 was published on 2026-06-15. The README does not document a migration path from 2.x to 3.x, and the release notes given here do not describe what changes in the alpha, so treat the major-version bump as an open question rather than a scheduled upgrade.
Upgrade cost is low in the ordinary case. The runtime dependency set is three packages: append-field, busboy and type-is. There is no plugin ecosystem inside the package to keep in sync, and the public surface is the multer() factory plus .single, .array, .fields and .none. The parts that break are usually your own: a custom filename function, a fileFilter, or a storage engine written against an interface that a major version could change. Those are the things to re-read before moving to 3.x.
Licence is MIT, stated in package.json and present as a LICENSE file at the repository root. The package is published with a files allowlist covering LICENSE, index.js, storage/ and lib/, so what you install is the runtime code and the two storage engines. MIT is permissive and imposes no source-disclosure obligation on your application. That is a factual note about the licence text, not legal advice; if you redistribute Multer itself or embed it in a product with unusual compliance requirements, have counsel read the LICENSE file rather than this article.
Editorial conclusion
Adopt Multer when your Express routes need multipart/form-data parsed and you are content to own validation, file naming and storage cleanup yourself; it is the wrong tool for JSON-only APIs, for non-multipart forms, and for teams that want virus scanning, resizing or cloud uploads handled inside the middleware. Before committing, verify the Node version against the engines field (>= 10.16.0), confirm whether you need dest or a custom storage engine, and read StorageEngine.md if you intend to write one, because the README defers that to a separate document.
Frequently asked questions
What is Multer used for?
It is Node.js middleware for handling multipart/form-data, which the README describes as primarily used for uploading files. It adds a body object and a file or files object to the request so text fields and uploaded files are both accessible in your route handler.
What are some alternatives to Multer?
The README states that Multer is written on top of busboy, so busboy itself is the layer underneath and can be used directly if you want to handle the multipart stream yourself. For storage, Multer ships DiskStorage and MemoryStorage and the README notes that more engines are available from third parties.
How do you install Multer in Node.js?
The README gives a single command, npm install multer. The package's engines field requires Node >= 10.16.0, so check your runtime version before installing.
How to use Multer in Express?
Require multer, create an instance with a dest or storage option, and pass a method such as upload.single('avatar') as route middleware. The field name you pass must match the name attribute in the HTML form, and the form needs enctype="multipart/form-data".
How to use Multer in Node.js?
The same middleware works in any Node.js application that exposes the request object Multer expects, though the README's examples are written for Express. Multer adds req.body and req.file or req.files after parsing, and the storage option decides whether files land on disk or in memory.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/expressjs-multer)