multipart-parser
The multipart-parser package provides low-level streaming parsing of multipart/form-data request bodies. It works in any JavaScript runtime including Node.js, Deno, Bun, Cloudflare Workers, and browsers.
Most applications should use form-data-parser instead, which builds on this package and provides a higher-level API. Use multipart-parser directly when you need fine-grained control over how individual parts are processed.
Installation
The multipart parser is included with Remix. No additional installation is needed.
Import
import {
parseMultipart,
parseMultipartStream,
MultipartParser,
isMultipartRequest,
getMultipartBoundary,
} from 'remix/multipart-parser'A Node.js-specific sub-export is also available:
import { ... } from 'remix/multipart-parser/node'API
isMultipartRequest(request)
Returns true if the request's Content-Type header indicates multipart/form-data.
function isMultipartRequest(request: Request): booleanif (!isMultipartRequest(request)) {
return new Response('Expected multipart form data', { status: 400 })
}getMultipartBoundary(request)
Extracts the boundary string from the request's Content-Type header. Returns null if the header is missing or does not contain a boundary.
function getMultipartBoundary(request: Request): string | nulllet boundary = getMultipartBoundary(request)
// e.g. '----WebKitFormBoundary7MA4YWxkTrZu0gW'parseMultipart(body, boundary)
Parses a complete multipart body and returns an array of parts. Each part includes its headers and body as a Uint8Array.
function parseMultipart(
body: ReadableStream<Uint8Array> | Uint8Array,
boundary: string,
): Promise<MultipartPart[]>let boundary = getMultipartBoundary(request)!
let parts = await parseMultipart(request.body!, boundary)
for (let part of parts) {
console.log(part.headers) // Headers object
console.log(part.body) // Uint8Array
}parseMultipartStream(body, boundary)
Returns an async iterator that yields parts as they are parsed from the stream. This is more memory-efficient than parseMultipart for large uploads because it processes parts one at a time.
function parseMultipartStream(
body: ReadableStream<Uint8Array>,
boundary: string,
): AsyncIterableIterator<MultipartPart>let boundary = getMultipartBoundary(request)!
for await (let part of parseMultipartStream(request.body!, boundary)) {
let disposition = part.headers.get('Content-Disposition')
console.log(disposition) // 'form-data; name="file"; filename="photo.jpg"'
}MultipartParser
A low-level class for incremental multipart parsing. Use this when you need full control over buffering and backpressure.
class MultipartParser {
constructor(boundary: string, options?: MultipartParserOptions)
write(chunk: Uint8Array): void
end(): void
onPart(callback: (part: MultipartPart) => void): void
}let parser = new MultipartParser(boundary, {
maxHeaderSize: 8192,
maxFileSize: 10 * 1024 * 1024,
maxParts: 100,
maxTotalSize: 50 * 1024 * 1024,
})
parser.onPart((part) => {
console.log(part.headers.get('Content-Disposition'))
})
for await (let chunk of request.body!) {
parser.write(chunk)
}
parser.end()Options
| Option | Type | Default | Description |
|---|---|---|---|
maxHeaderSize | number | 8192 | Maximum size in bytes for a single part's headers. |
maxFileSize | number | Infinity | Maximum size in bytes for a single file part's body. |
maxParts | number | Infinity | Maximum number of parts allowed in the request. |
maxTotalSize | number | Infinity | Maximum total size in bytes across all parts. |
Error Types
The parser throws specific error types when limits are exceeded:
MaxHeaderSizeExceededError-- A part's headers exceededmaxHeaderSize.MaxFileSizeExceededError-- A file part's body exceededmaxFileSize.MaxPartsExceededError-- The number of parts exceededmaxParts.MaxTotalSizeExceededError-- The total request body exceededmaxTotalSize.
import {
MaxFileSizeExceededError,
MaxPartsExceededError,
} from 'remix/multipart-parser'
try {
let parts = await parseMultipart(request.body!, boundary)
} catch (error) {
if (error instanceof MaxFileSizeExceededError) {
return new Response('File too large', { status: 413 })
}
if (error instanceof MaxPartsExceededError) {
return new Response('Too many fields', { status: 400 })
}
throw error
}Examples
Streaming File Upload
Process a large file upload without buffering the entire body in memory:
import {
parseMultipartStream,
isMultipartRequest,
getMultipartBoundary,
} from 'remix/multipart-parser'
router.map(uploadRoute, async ({ request }) => {
if (!isMultipartRequest(request)) {
return new Response('Bad Request', { status: 400 })
}
let boundary = getMultipartBoundary(request)!
for await (let part of parseMultipartStream(request.body!, boundary)) {
let disposition = part.headers.get('Content-Disposition')
let filename = disposition?.match(/filename="(.+?)"/)?.[1]
if (filename) {
// Stream the file to storage
await saveToStorage(filename, part.body)
}
}
return new Response('Uploaded', { status: 200 })
})With Size Limits
Protect against oversized uploads:
import { parseMultipart, getMultipartBoundary } from 'remix/multipart-parser'
let parts = await parseMultipart(request.body!, getMultipartBoundary(request)!, {
maxFileSize: 5 * 1024 * 1024, // 5 MB per file
maxParts: 10, // 10 fields max
maxTotalSize: 20 * 1024 * 1024, // 20 MB total
})Related
- form-data-parser --- Higher-level form data parsing with upload handlers.
- File Uploads Guide --- End-to-end guide to file uploads in Remix.