formidable
A Node.js module for parsing form data, especially file uploads.
If you have any how-to kind of questions, please read the Contributing
Guide and Code of Conduct
documents.
For bugs reports and feature requests, please create an
issue or ping @tunnckoCore / @3a1FcBx0
at Twitter.
This project is semantically versioned and available as
part of the Tidelift Subscription for professional grade
assurances, enhanced support and security.
Learn more.
The maintainers of formidable
and thousands of other packages are working
with Tidelift to deliver commercial support and maintenance for the Open Source
dependencies you use to build your applications. Save time, reduce risk, and
improve code health, while paying the maintainers of the exact dependencies you
use.
Project Status: Maintained
Check VERSION NOTES for more information on v1, v2, and v3 plans, NPM dist-tags and branches.
This module was initially developed by
@felixge for
Transloadit, a service focused on uploading and
encoding images and videos. It has been battle-tested against hundreds of GBs of
file uploads from a large variety of clients and is considered production-ready
and is used in production for years.
Currently, we are few maintainers trying to deal with it. :) More contributors
are always welcome! :heart: Jump on
issue #412 which is
closed, but if you are interested we can discuss it and add you after strict
rules, like enabling Two-Factor Auth in your npm and GitHub accounts.
Highlights
Install
This package is a dual ESM/commonjs package.
This project requires Node.js >= 10.13
. Install it using
yarn or npm.
We highly
recommend to use Yarn when you think to contribute to this project.
This is a low-level package, and if you're using a high-level framework it may
already be included. Check the examples below and the examples/ folder.
# v2
npm install formidable@v2
# v3
npm install formidable
npm install formidable@v3
Note: Future not ready releases will be published on *-next
dist-tags for the corresponding version.
Examples
For more examples look at the examples/
directory.
with Node.js http module
Parse an incoming file upload, with the
Node.js's built-in http
module.
import http from 'node:http';
import formidable, {errors as formidableErrors} from 'formidable';
const server = http.createServer(async (req, res) => {
if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') {
const form = formidable({});
let fields;
let files;
try {
[fields, files] = await form.parse(req);
} catch (err) {
if (err.code === formidableErrors.maxFieldsExceeded) {
}
console.error(err);
res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' });
res.end(String(err));
return;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ fields, files }, null, 2));
return;
}
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<h2>With Node.js <code>"http"</code> module</h2>
<form action="/api/upload" enctype="multipart/form-data" method="post">
<div>Text field title: <input type="text" name="title" /></div>
<div>File: <input type="file" name="multipleFiles" multiple="multiple" /></div>
<input type="submit" value="Upload" />
</form>
`);
});
server.listen(8080, () => {
console.log('Server listening on http://localhost:8080/ ...');
});
with Express.js
There are multiple variants to do this, but Formidable just need Node.js Request
stream, so something like the following example should work just fine, without
any third-party Express.js middleware.
Or try the
examples/with-express.js
import express from 'express';
import formidable from 'formidable';
const app = express();
app.get('/', (req, res) => {
res.send(`
<h2>With <code>"express"</code> npm package</h2>
<form action="/api/upload" enctype="multipart/form-data" method="post">
<div>Text field title: <input type="text" name="title" /></div>
<div>File: <input type="file" name="someExpressFiles" multiple="multiple" /></div>
<input type="submit" value="Upload" />
</form>
`);
});
app.post('/api/upload', (req, res, next) => {
const form = formidable({});
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}
res.json({ fields, files });
});
});
app.listen(3000, () => {
console.log('Server listening on http://localhost:3000 ...');
});
with Koa and Formidable
Of course, with Koa v1, v2 or future v3 the things
are very similar. You can use formidable
manually as shown below or through
the koa-better-body package which is
using formidable
under the hood and support more features and different
request bodies, check its documentation for more info.
Note: this example is assuming Koa v2. Be aware that you should pass ctx.req
which is Node.js's Request, and NOT the ctx.request
which is Koa's Request
object - there is a difference.
import Koa from 'Koa';
import formidable from 'formidable';
const app = new Koa();
app.on('error', (err) => {
console.error('server error', err);
});
app.use(async (ctx, next) => {
if (ctx.url === '/api/upload' && ctx.method.toLowerCase() === 'post') {
const form = formidable({});
await new Promise((resolve, reject) => {
form.parse(ctx.req, (err, fields, files) => {
if (err) {
reject(err);
return;
}
ctx.set('Content-Type', 'application/json');
ctx.status = 200;
ctx.state = { fields, files };
ctx.body = JSON.stringify(ctx.state, null, 2);
resolve();
});
});
await next();
return;
}
ctx.set('Content-Type', 'text/html');
ctx.status = 200;
ctx.body = `
<h2>With <code>"koa"</code> npm package</h2>
<form action="/api/upload" enctype="multipart/form-data" method="post">
<div>Text field title: <input type="text" name="title" /></div>
<div>File: <input type="file" name="koaFiles" multiple="multiple" /></div>
<input type="submit" value="Upload" />
</form>
`;
});
app.use((ctx) => {
console.log('The next middleware is called');
console.log('Results:', ctx.state);
});
app.listen(3000, () => {
console.log('Server listening on http://localhost:3000 ...');
});
Benchmarks
The benchmark is quite old, from the old codebase. But maybe quite true though.
Previously the numbers was around ~500 mb/sec. Currently with moving to the new
Node.js Streams API it's faster. You can clearly see the differences between the
Node versions.
Note: a lot better benchmarking could and should be done in future.
Benchmarked on 8GB RAM, Xeon X3440 (2.53 GHz, 4 cores, 8 threads)
~/github/node-formidable master
❯ nve --parallel 8 10 12 13 node benchmark/bench-multipart-parser.js
⬢ Node 8
1261.08 mb/sec
⬢ Node 10
1113.04 mb/sec
⬢ Node 12
2107.00 mb/sec
⬢ Node 13
2566.42 mb/sec
API
Formidable / IncomingForm
All shown are equivalent.
Please pass options
to the function/constructor, not by assigning
them to the instance form
import formidable from 'formidable';
const form = formidable(options);
Options
See it's defaults in src/Formidable.js DEFAULT_OPTIONS
(the DEFAULT_OPTIONS
constant).
-
options.encoding
{string} - default 'utf-8'
; sets encoding for
incoming form fields,
-
options.uploadDir
{string} - default os.tmpdir()
; the directory for
placing file uploads in. You can move them later by using fs.rename()
.
-
options.keepExtensions
{boolean} - default false
; to include the
extensions of the original files or not
-
options.allowEmptyFiles
{boolean} - default false
; allow upload empty
files
-
options.minFileSize
{number} - default 1
(1byte); the minium size of
uploaded file.
-
options.maxFiles
{number} - default Infinity
;
limit the amount of uploaded files, set Infinity for unlimited
-
options.maxFileSize
{number} - default 200 * 1024 * 1024
(200mb);
limit the size of each uploaded file.
-
options.maxTotalFileSize
{number} - default options.maxFileSize
;
limit the size of the batch of uploaded files.
-
options.maxFields
{number} - default 1000
; limit the number of fields, set Infinity for unlimited
-
options.maxFieldsSize
{number} - default 20 * 1024 * 1024
(20mb);
limit the amount of memory all fields together (except files) can allocate in
bytes.
-
options.hashAlgorithm
{string | false} - default false
; include checksums calculated
for incoming files, set this to some hash algorithm, see
crypto.createHash
for available algorithms
-
options.fileWriteStreamHandler
{function} - default null
, which by
default writes to host machine file system every file parsed; The function
should return an instance of a
Writable stream
that will receive the uploaded file data. With this option, you can have any
custom behavior regarding where the uploaded file data will be streamed for.
If you are looking to write the file uploaded in other types of cloud storages
(AWS S3, Azure blob storage, Google cloud storage) or private file storage,
this is the option you're looking for. When this option is defined the default
behavior of writing the file in the host machine file system is lost.
-
options.filename
{function} - default undefined
Use it to control
newFilename. Must return a string. Will be joined with options.uploadDir.
-
options.filter
{function} - default function that always returns true.
Use it to filter files before they are uploaded. Must return a boolean. Will not make the form.parse error
-
options.createDirsFromUploads
{boolean} - default false. If true, makes direct folder uploads possible. Use <input type="file" name="folders" webkitdirectory directory multiple>
to create a form to upload folders. Has to be used with the options options.uploadDir
and options.filename
where options.filename
has to return a string with the character /
for folders to be created. The base will be options.uploadDir
.
options.filename
{function} function (name, ext, part, form) -> string
where part can be decomposed as
const { originalFilename, mimetype} = part;
Note: If this size of combined fields, or size of some file is exceeded, an
'error'
event is fired.
form.bytesReceived;
form.bytesExpected;
options.filter
{function} function ({name, originalFilename, mimetype}) -> boolean
Behaves like Array.filter: Returning false will simply ignore the file and go to the next.
const options = {
filter: function ({name, originalFilename, mimetype}) {
return mimetype && mimetype.includes("image");
}
};
Note: use an outside variable to cancel all uploads upon the first error
Note: use form.emit('error') to make form.parse error
let cancelUploads = false;
const options = {
filter: function ({name, originalFilename, mimetype}) {
const valid = mimetype && mimetype.includes("image");
if (!valid) {
form.emit('error', new formidableErrors.default('invalid type', 0, 400));
cancelUploads = true;
}
return valid && !cancelUploads;
}
};
.parse(request, ?callback)
Parses an incoming Node.js request
containing form data. If callback
is not provided a promise is returned.
const form = formidable({ uploadDir: __dirname });
form.parse(req, (err, fields, files) => {
console.log('fields:', fields);
console.log('files:', files);
});
const [fields, files] = await form.parse(req);
You may overwrite this method if you are interested in directly accessing the
multipart stream. Doing so will disable any 'field'
/ 'file'
events
processing which would occur otherwise, making you fully responsible for
handling the processing.
About uploadDir
, given the following directory structure
project-name
├── src
│ └── server.js
│
└── uploads
└── image.jpg
__dirname
would be the same directory as the source file itself (src)
`${__dirname}/../uploads`
to put files in uploads.
Omitting __dirname
would make the path relative to the current working directory. This would be the same if server.js is launched from src but not project-name.
null
will use default which is os.tmpdir()
Note: If the directory does not exist, the uploaded files are silently discarded. To make sure it exists:
import {createNecessaryDirectoriesSync} from "filesac";
const uploadPath = `${__dirname}/../uploads`;
createNecessaryDirectoriesSync(`${uploadPath}/x`);
In the example below, we listen on couple of events and direct them to the
data
listener, so you can do whatever you choose there, based on whether its
before the file been emitted, the header value, the header name, on field, on
file and etc.
Or the other way could be to just override the form.onPart
as it's shown a bit
later.
form.once('error', console.error);
form.on('fileBegin', (formname, file) => {
form.emit('data', { name: 'fileBegin', formname, value: file });
});
form.on('file', (formname, file) => {
form.emit('data', { name: 'file', formname, value: file });
});
form.on('field', (fieldName, fieldValue) => {
form.emit('data', { name: 'field', key: fieldName, value: fieldValue });
});
form.once('end', () => {
console.log('Done!');
});
form.on('data', ({ name, key, value, buffer, start, end, formname, ...more }) => {
if (name === 'partBegin') {
}
if (name === 'partData') {
}
if (name === 'headerField') {
}
if (name === 'headerValue') {
}
if (name === 'headerEnd') {
}
if (name === 'headersEnd') {
}
if (name === 'field') {
console.log('field name:', key);
console.log('field value:', value);
}
if (name === 'file') {
console.log('file:', formname, value);
}
if (name === 'fileBegin') {
console.log('fileBegin:', formname, value);
}
});
.use(plugin: Plugin)
A method that allows you to extend the Formidable library. By default we include
4 plugins, which essentially are adapters to plug the different built-in parsers.
The plugins added by this method are always enabled.
See src/plugins/ for more detailed look on default plugins.
The plugin
param has such signature:
function(formidable: Formidable, options: Options): void;
The architecture is simple. The plugin
is a function that is passed with the
Formidable instance (the form
across the README examples) and the options.
Note: the plugin function's this
context is also the same instance.
const form = formidable({ keepExtensions: true });
form.use((self, options) => {
console.log('woohoo, custom plugin');
});
form.parse(req, (error, fields, files) => {
console.log('done!');
});
Important to note, is that inside plugin this.options
, self.options
and
options
MAY or MAY NOT be the same. General best practice is to always use the
this
, so you can later test your plugin independently and more easily.
If you want to disable some parsing capabilities of Formidable, you can disable
the plugin which corresponds to the parser. For example, if you want to disable
multipart parsing (so the src/parsers/Multipart.js
which is used in src/plugins/multipart.js), then
you can remove it from the options.enabledPlugins
, like so
import formidable, {octetstream, querystring, json} from "formidable";
const form = formidable({
hashAlgorithm: 'sha1',
enabledPlugins: [octetstream, querystring, json],
});
Be aware that the order MAY be important too. The names corresponds 1:1 to
files in src/plugins/ folder.
Pull requests for new built-in plugins MAY be accepted - for example, more
advanced querystring parser. Add your plugin as a new file in src/plugins/
folder (lowercased) and follow how the other plugins are made.
form.onPart
If you want to use Formidable to only handle certain parts for you, you can do
something similar. Or see
#387 for
inspiration, you can for example validate the mime-type.
const form = formidable();
form.onPart = (part) => {
part.on('data', (buffer) => {
});
};
For example, force Formidable to be used only on non-file "parts" (i.e., html
fields)
const form = formidable();
form.onPart = function (part) {
if (part.originalFilename === '' || !part.mimetype) {
form._handlePart(part);
}
};
File
export interface File {
file.size: number;
file.filepath: string;
file.originalFilename: string | null;
file.newFilename: string | null;
file.mimetype: string | null;
file.mtime: Date | null;
file.hashAlgorithm: false | |'sha1' | 'md5' | 'sha256'
file.hash: string | object | null;
}
file.toJSON()
This method returns a JSON-representation of the file, allowing you to
JSON.stringify()
the file which is useful for logging and responding to
requests.
Events
'progress'
Emitted after each incoming chunk of data that has been parsed. Can be used to
roll your own progress bar. Warning Use this only for server side progress bar. On the client side better use XMLHttpRequest
with xhr.upload.onprogress =
form.on('progress', (bytesReceived, bytesExpected) => {});
'field'
Emitted whenever a field / value pair has been received.
form.on('field', (name, value) => {});
'fileBegin'
Emitted whenever a new file is detected in the upload stream. Use this event if
you want to stream the file to somewhere else while buffering the upload on the
file system.
form.on('fileBegin', (formName, file) => {
});
'file'
Emitted whenever a field / file pair has been received. file
is an instance of
File
.
form.on('file', (formname, file) => {
});
'error'
Emitted when there is an error processing the incoming form. A request that
experiences an error is automatically paused, you will have to manually call
request.resume()
if you want the request to continue firing 'data'
events.
May have error.httpCode
and error.code
attached.
form.on('error', (err) => {});
'aborted'
Emitted when the request was aborted by the user. Right now this can be due to a
'timeout' or 'close' event on the socket. After this event is emitted, an
error
event will follow. In the future there will be a separate 'timeout'
event (needs a change in the node core).
form.on('aborted', () => {});
'end'
Emitted when the entire request has been received, and all contained files have
finished flushing to disk. This is a great place for you to send your response.
form.on('end', () => {});
Helpers
firstValues
Gets first values of fields, like pre 3.0.0 without multiples pass in a list of optional exceptions where arrays of strings is still wanted (<select multiple>
for example)
import { firstValues } from 'formidable/src/helpers/firstValues.js';
form.parse(request, async (error, fieldsMultiple, files) => {
if (error) {
}
const exceptions = ['thisshouldbeanarray'];
const fieldsSingle = firstValues(form, fieldsMultiple, exceptions);
readBooleans
Html form input type="checkbox" only send the value "on" if checked,
convert it to booleans for each input that is expected to be sent as a checkbox, only use after firstValues or similar was called.
import { firstValues } from 'formidable/src/helpers/firstValues.js';
import { readBooleans } from 'formidable/src/helpers/readBooleans.js';
form.parse(request, async (error, fieldsMultiple, files) => {
if (error) {
}
const fieldsSingle = firstValues(form, fieldsMultiple);
const expectedBooleans = ['checkbox1', 'wantsNewsLetter', 'hasACar'];
const fieldsWithBooleans = readBooleans(fieldsSingle, expectedBooleans);
Changelog
./CHANGELOG.md
Ports & Credits
Contributing
If the documentation is unclear or has a typo, please click on the page's Edit
button (pencil icon) and suggest a correction. If you would like to help us fix
a bug or add a new feature, please check our Contributing
Guide. Pull requests are welcome!
Thanks goes to these wonderful people
(emoji key):
From a Felix blog post:
- Sven Lito for fixing bugs and merging patches
- egirshov for contributing many improvements to the node-formidable multipart parser
- Andrew Kelley for also helping with fixing bugs and making improvements
- Mike Frey for contributing JSON support
License
Formidable is licensed under the MIT License.