Skip to main content
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

npm i remix

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>
request
Request
required
The incoming request to parse. Must have Content-Type: multipart/form-data or another multipart/* type.
options
ParseMultipartOptions
Configuration options for parsing.

ParseMultipartOptions

maxFileSize
number
Maximum file size in bytes. Throws MaxFileSizeExceededError if exceeded.Default: Infinity (no limit)
maxHeaderSize
number
Maximum size of part headers in bytes.Default: 8192 (8 KB)

MultipartPart

Represents a single part in a multipart message.

Properties

name
string
The field name from the Content-Disposition header.
filename
string | null
The filename if this is a file upload, or null for regular fields.
isFile
boolean
true if this part has a filename (file upload), false otherwise.
mediaType
string
The media type from the Content-Type header, or 'text/plain' if not specified.
size
number
The size of the part data in bytes.
arrayBuffer
ArrayBuffer
The part data as an ArrayBuffer.
bytes
Uint8Array
The part data as a Uint8Array.
text
string
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
contentType
string | null
required
The Content-Type header value.
returns
string | null
The boundary string, or null if not found.

isMultipartRequest

Checks if a request is a multipart request.
function isMultipartRequest(
  request: Request
): boolean
request
Request
required
The request to check.
returns
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>
message
Uint8Array
required
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()
}

MaxHeaderSizeExceededError

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 {}

Performance

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

Build docs developers (and LLMs) love