Fast streaming multipart parsing for JavaScript. The multipart-parser package processes multipart bodies incrementally so large uploads can be handled without buffering the entire payload in memory.
Installation
Features
- File Upload Parsing - Parse file uploads (
multipart/form-data) with automatic field and file detection
- Full Multipart Support - Support for all
multipart/* content types
- Convenient API -
MultipartPart with arrayBuffer, bytes, text, size, and metadata access
- File Size Limiting - Built-in file size limiting to prevent abuse
- Node.js Support - First-class Node.js support with
http.IncomingMessage compatibility
- Streaming - Processes data incrementally without buffering
- Runtime Agnostic - Works in Node.js, Bun, Deno, and Cloudflare Workers
Basic Usage
Parsing File Uploads
The most common use case is handling file uploads with parseMultipartRequest:
import {
MultipartParseError,
parseMultipartRequest,
} from 'remix/multipart-parser'
async function handleRequest(request: Request): Promise<Response> {
try {
for await (let part of parseMultipartRequest(request)) {
if (part.isFile) {
// Access file data
let buffer = part.arrayBuffer // ArrayBuffer
console.log(`File: ${part.filename} (${buffer.byteLength} bytes)`)
console.log(`Content type: ${part.mediaType}`)
console.log(`Field name: ${part.name}`)
// Save to disk, upload to cloud storage, etc.
await saveFile(part.filename, part.bytes)
} else {
let text = part.text // string
console.log(`Field: ${part.name} = ${JSON.stringify(text)}`)
}
}
return new Response('Upload successful')
} catch (error) {
if (error instanceof MultipartParseError) {
return new Response('Invalid multipart request', { status: 400 })
}
throw error
}
}
API Reference
parseMultipartRequest
Parses a multipart request and yields parts as they are parsed.
function parseMultipartRequest(
request: Request,
options?: ParseMultipartOptions
): AsyncGenerator<MultipartPart>
The incoming request to parse. Must have Content-Type: multipart/form-data or another multipart/* type.
Configuration options for parsing.
ParseMultipartOptions
Maximum file size in bytes. Throws MaxFileSizeExceededError if exceeded.Default: Infinity (no limit)
Maximum size of part headers in bytes.Default: 8192 (8 KB)
MultipartPart
Represents a single part in a multipart message.
Properties
The field name from the Content-Disposition header.
The filename if this is a file upload, or null for regular fields.
true if this part has a filename (file upload), false otherwise.
The media type from the Content-Type header, or 'text/plain' if not specified.
The size of the part data in bytes.
The part data as an ArrayBuffer.
The part data as a Uint8Array.
The part data decoded as UTF-8 text.
getMultipartBoundary
Extracts the multipart boundary from a Content-Type header.
function getMultipartBoundary(
contentType: string | null
): string | null
The Content-Type header value.
The boundary string, or null if not found.
isMultipartRequest
Checks if a request is a multipart request.
function isMultipartRequest(
request: Request
): boolean
true if the request has a multipart/* Content-Type.
Examples
Limiting File Upload Size
import {
MaxFileSizeExceededError,
parseMultipartRequest,
} from 'remix/multipart-parser'
const oneMb = Math.pow(2, 20)
const maxFileSize = 10 * oneMb
async function handleRequest(request: Request): Promise<Response> {
try {
for await (let part of parseMultipartRequest(request, { maxFileSize })) {
// Process part
}
return new Response('Success')
} catch (error) {
if (error instanceof MaxFileSizeExceededError) {
return new Response('File too large', { status: 413 })
}
throw error
}
}
Filtering File Types
for await (let part of parseMultipartRequest(request)) {
if (part.isFile) {
// Only accept images
if (!part.mediaType.startsWith('image/')) {
return new Response('Only images are allowed', { status: 400 })
}
await saveFile(part.filename, part.bytes)
}
}
Saving to Different Locations
import * as fsp from 'node:fs/promises'
for await (let part of parseMultipartRequest(request)) {
if (part.isFile && part.name === 'avatar') {
await fsp.writeFile(`./uploads/avatars/${userId}.jpg`, part.bytes)
} else if (part.isFile && part.name === 'documents') {
await fsp.writeFile(`./uploads/documents/${part.filename}`, part.bytes)
} else {
console.log(`Field ${part.name}:`, part.text)
}
}
Cloud Storage Upload
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
let s3 = new S3Client({})
for await (let part of parseMultipartRequest(request)) {
if (part.isFile) {
await s3.send(
new PutObjectCommand({
Bucket: 'my-bucket',
Key: `uploads/${part.filename}`,
Body: part.bytes,
ContentType: part.mediaType,
})
)
}
}
Node.js Support
For Node.js servers using http.IncomingMessage, use the Node.js-specific module:
import * as http from 'node:http'
import {
MultipartParseError,
parseMultipartRequest,
} from 'remix/multipart-parser/node'
let server = http.createServer(async (req, res) => {
try {
// Works directly with http.IncomingMessage
for await (let part of parseMultipartRequest(req)) {
if (part.isFile) {
console.log(`Received file: ${part.filename}`)
}
}
res.writeHead(200)
res.end('Upload successful')
} catch (error) {
if (error instanceof MultipartParseError) {
res.writeHead(400)
res.end('Invalid multipart request')
} else {
res.writeHead(500)
res.end('Internal server error')
}
}
})
server.listen(8080)
Low-Level API
For working directly with multipart boundaries and streams:
parseMultipart
Synchronous parser for multipart data in a buffer.
function parseMultipart(
message: Uint8Array,
options: MultipartParserOptions
): Generator<MultipartPart>
The complete multipart message data.
options
MultipartParserOptions
required
Parser options including the boundary.
import { parseMultipart } from 'remix/multipart-parser'
let message = new Uint8Array(/* ... */)
let boundary = '----WebKitFormBoundary56eac3x'
for (let part of parseMultipart(message, { boundary })) {
console.log(part.name, part.text)
}
parseMultipartStream
Asynchronous parser for streaming multipart data.
function parseMultipartStream(
message: ReadableStream<Uint8Array>,
options: MultipartParserOptions
): AsyncGenerator<MultipartPart>
message
ReadableStream<Uint8Array>
required
The multipart message stream.
options
MultipartParserOptions
required
Parser options including the boundary.
import { parseMultipartStream } from 'remix/multipart-parser'
let message = new ReadableStream(/* ... */)
let boundary = '----WebKitFormBoundary56eac3x'
for await (let part of parseMultipartStream(message, { boundary })) {
console.log(part.name, part.text)
}
Error Handling
MultipartParseError
Base error class for multipart parsing errors.
class MultipartParseError extends Error {
constructor(message: string)
}
MaxFileSizeExceededError
Thrown when a file exceeds the maxFileSize limit.
class MaxFileSizeExceededError extends MultipartParseError {
constructor()
}
Thrown when part headers exceed the maxHeaderSize limit.
class MaxHeaderSizeExceededError extends MultipartParseError {
constructor()
}
Example Error Handling
import {
MultipartParseError,
MaxFileSizeExceededError,
MaxHeaderSizeExceededError,
parseMultipartRequest,
} from 'remix/multipart-parser'
try {
for await (let part of parseMultipartRequest(request, {
maxFileSize: 10 * 1024 * 1024, // 10 MB
})) {
// Process part
}
} catch (error) {
if (error instanceof MaxFileSizeExceededError) {
return new Response('File too large (max 10 MB)', { status: 413 })
} else if (error instanceof MaxHeaderSizeExceededError) {
return new Response('Part headers too large', { status: 400 })
} else if (error instanceof MultipartParseError) {
return new Response('Invalid multipart data', { status: 400 })
}
throw error
}
Type Definitions
interface ParseMultipartOptions {
maxFileSize?: number
maxHeaderSize?: number
}
interface MultipartParserOptions {
boundary: string
maxFileSize?: number
maxHeaderSize?: number
}
class MultipartPart {
readonly name: string
readonly filename: string | null
readonly isFile: boolean
readonly mediaType: string
readonly size: number
readonly arrayBuffer: ArrayBuffer
readonly bytes: Uint8Array
readonly text: string
}
class MultipartParseError extends Error {}
class MaxFileSizeExceededError extends MultipartParseError {}
class MaxHeaderSizeExceededError extends MultipartParseError {}
The multipart-parser is designed for exceptional performance. In benchmarks, it performs as fast or faster than popular libraries like busboy:
- 1 small file: ~0.01ms
- 1 large file: ~1ms
- 100 small files: ~0.04ms
- 5 large files: ~10ms
The streaming design ensures minimal memory usage regardless of payload size.
Best Practices
Always Set File Size Limits
// ✅ Good: Set reasonable limits
for await (let part of parseMultipartRequest(request, {
maxFileSize: 50 * 1024 * 1024, // 50 MB
})) {
// ...
}
// ❌ Bad: No limits (DoS risk)
for await (let part of parseMultipartRequest(request)) {
// ...
}
Validate File Types
// ✅ Good: Validate media types
if (part.isFile && !allowedTypes.includes(part.mediaType)) {
return new Response('Invalid file type', { status: 400 })
}
Use Streaming for Large Files
// ✅ Good: Stream to storage
for await (let part of parseMultipartRequest(request)) {
if (part.isFile) {
// parts are already buffered, but you can use the
// low-level API for true streaming if needed
await storage.put(part.filename, part.bytes)
}
}
- form-data-parser - High-level FormData parser using multipart-parser
- headers - Used internally for parsing part headers