πŸš€ Big News:Socket Has Acquired Secure Annex.Learn More β†’
Socket
Book a DemoSign in
Socket

@remix-run/multipart-parser

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@remix-run/multipart-parser

A fast, efficient parser for multipart streams in any JavaScript environment

latest
Source
npmnpm
Version
0.16.0
Version published
Weekly downloads
23K
-10.53%
Maintainers
1
Weekly downloads
Β 
Created
Source

multipart-parser

Fast streaming multipart parsing for JavaScript. multipart-parser processes multipart bodies incrementally so large uploads can be handled without buffering the entire multipart payload in memory.

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 (mixed, alternative, related, etc.)
  • Convenient API - MultipartPart API with arrayBuffer, bytes, text, size, and metadata access
  • Built-in Limits - Header, per-part, part-count, and aggregate-size limits to prevent abuse
  • Node.js Support - First-class Node.js support with native http.IncomingMessage compatibility
  • Runtime Demos - Demos for every major runtime

Installation

npm i remix

Usage

The most common use case for multipart-parser is handling file uploads when you're building a web server. For this case, the parseMultipartRequest function is your friend. It automatically validates the request is multipart/form-data, extracts the multipart boundary from the Content-Type header, parses all fields and files in the request.body stream, and gives each one to you as a MultipartPart object with a rich API for accessing its metadata and content.

import { MultipartParseError, parseMultipartRequest } from 'remix/multipart-parser'

async function handleRequest(request: Request): void {
  try {
    for await (let part of parseMultipartRequest(request)) {
      if (part.isFile) {
        // Access file data in multiple formats
        let buffer = part.arrayBuffer // ArrayBuffer
        console.log(`File received: ${part.filename} (${buffer.byteLength} bytes)`)
        console.log(`Content type: ${part.mediaType}`)
        console.log(`Field name: ${part.name}`)
        console.log(`Content-Type header: ${part.headers['content-type']}`)

        // Save to disk, upload to cloud storage, etc.
        await saveFile(part.filename, part.bytes)
      } else {
        let text = part.text // string
        console.log(`Field received: ${part.name} = ${JSON.stringify(text)}`)
      }
    }
  } catch (error) {
    if (error instanceof MultipartParseError) {
      console.error('Failed to parse multipart request:', error.message)
    } else {
      console.error('An unexpected error occurred:', error)
    }
  }
}

Part Headers

Each MultipartPart exposes decoded part headers as a plain object keyed by lower-case header name. Values are strings, and repeated headers are joined with , . Multipart part headers are parsed metadata from the request body, not native Headers objects, so access them with bracket notation:

for await (let part of parseMultipartRequest(request)) {
  let contentDisposition = part.headers['content-disposition']
  let contentType = part.headers['content-type']

  console.log(contentDisposition, contentType)
}

Size Limits

A common use case when handling file uploads is limiting the overall shape of incoming multipart bodies so malicious clients cannot force unbounded growth in memory. Use maxFileSize to limit each part, maxParts to limit how many parts are accepted, and maxTotalSize to limit aggregate part content across the entire request. multipart-parser applies finite defaults for each of these limits.

import {
  MultipartParseError,
  MaxFileSizeExceededError,
  MaxPartsExceededError,
  MaxTotalSizeExceededError,
  parseMultipartRequest,
} from 'remix/multipart-parser/node'

const oneMb = Math.pow(2, 20)
const limits = {
  maxFileSize: 10 * oneMb,
  maxParts: 100,
  maxTotalSize: 25 * oneMb,
}

async function handleRequest(request: Request): Promise<Response> {
  try {
    for await (let part of parseMultipartRequest(request, limits)) {
      // ...
    }
  } catch (error) {
    if (error instanceof MaxFileSizeExceededError) {
      return new Response('File size limit exceeded', { status: 413 })
    } else if (error instanceof MaxPartsExceededError) {
      return new Response('Too many multipart parts', { status: 413 })
    } else if (error instanceof MaxTotalSizeExceededError) {
      return new Response('Multipart request is too large', { status: 413 })
    } else if (error instanceof MultipartParseError) {
      return new Response('Failed to parse multipart request', { status: 400 })
    } else {
      console.error(error)
      return new Response('Internal Server Error', { status: 500 })
    }
  }
}

Node.js Bindings

The main module (import {} from 'remix/multipart-parser') assumes you're working with the fetch API (Request, ReadableStream, etc). Support for these interfaces was added to Node.js by the undici project in version 16.5.0.

If however you're building a server for Node.js that relies on node-specific APIs like http.IncomingMessage, stream.Readable, and buffer.Buffer (ala Express or http.createServer), multipart-parser ships with an additional module that works directly with these APIs.

import * as http from 'node:http'
import { MultipartParseError, parseMultipartRequest } from 'remix/multipart-parser/node'

let server = http.createServer(async (req, res) => {
  try {
    for await (let part of parseMultipartRequest(req)) {
      // ...
    }
  } catch (error) {
    if (error instanceof MultipartParseError) {
      console.error('Failed to parse multipart request:', error.message)
    } else {
      console.error('An unexpected error occurred:', error)
    }
  }
})

server.listen(8080)

Low-level API

If you're working directly with multipart boundaries and buffers/streams of multipart data that are not necessarily part of a request, multipart-parser provides a low-level parseMultipart() API that you can use directly:

import { parseMultipart } from 'remix/multipart-parser'

let message = new Uint8Array(/* ... */)
let boundary = '----WebKitFormBoundary56eac3x'

for (let part of parseMultipart(message, { boundary })) {
  // ...
}

In addition, the parseMultipartStream function provides an async generator interface for multipart data in a ReadableStream:

import { parseMultipartStream } from 'remix/multipart-parser'

let message = new ReadableStream(/* ... */)
let boundary = '----WebKitFormBoundary56eac3x'

for await (let part of parseMultipartStream(message, { boundary })) {
  // ...
}

Demos

The demos directory contains a few working demos of how you can use this library:

  • demos/bun - using multipart-parser in Bun
  • demos/cf-workers - using multipart-parser in a Cloudflare Worker and storing file uploads in R2
  • demos/deno - using multipart-parser in Deno
  • demos/node - using multipart-parser in Node.js

Benchmark

multipart-parser is designed to be as efficient as possible, operating on streams of data and rarely buffering in common usage. This design yields exceptional performance when handling multipart payloads of any size. In benchmarks, multipart-parser is as fast or faster than busboy.

The results of running the benchmarks on my laptop:

> @remix-run/multipart-parser@0.10.1 bench:node /Users/michael/Projects/remix-the-web/packages/multipart-parser
> node ./bench/runner.ts

Platform: Darwin (24.5.0)
CPU: Apple M1 Pro
Date: 6/13/2025, 12:27:09 PM
Node.js v24.0.2
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ (index)          β”‚ 1 small file     β”‚ 1 large file     β”‚ 100 small files  β”‚ 5 large files     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ multipart-parser β”‚ '0.01 ms Β± 0.03' β”‚ '1.08 ms Β± 0.08' β”‚ '0.04 ms Β± 0.01' β”‚ '10.50 ms Β± 0.38' β”‚
β”‚ multipasta       β”‚ '0.02 ms Β± 0.06' β”‚ '1.07 ms Β± 0.02' β”‚ '0.15 ms Β± 0.02' β”‚ '10.46 ms Β± 0.11' β”‚
β”‚ busboy           β”‚ '0.06 ms Β± 0.17' β”‚ '3.07 ms Β± 0.24' β”‚ '0.24 ms Β± 0.05' β”‚ '29.85 ms Β± 0.18' β”‚
β”‚ @fastify/busboy  β”‚ '0.05 ms Β± 0.13' β”‚ '1.23 ms Β± 0.09' β”‚ '0.45 ms Β± 0.22' β”‚ '11.81 ms Β± 0.11' β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

> @remix-run/multipart-parser@0.10.1 bench:bun /Users/michael/Projects/remix-the-web/packages/multipart-parser
> bun run ./bench/runner.ts

Platform: Darwin (24.5.0)
CPU: Apple M1 Pro
Date: 6/13/2025, 12:27:31 PM
Bun 1.2.13
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  β”‚ 1 small file   β”‚ 1 large file   β”‚ 100 small files β”‚ 5 large files   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ multipart-parser β”‚ 0.01 ms Β± 0.04 β”‚ 0.86 ms Β± 0.09 β”‚ 0.04 ms Β± 0.01  β”‚ 8.32 ms Β± 0.26  β”‚
β”‚       multipasta β”‚ 0.02 ms Β± 0.07 β”‚ 0.87 ms Β± 0.03 β”‚ 0.25 ms Β± 0.21  β”‚ 8.27 ms Β± 0.09  β”‚
β”‚           busboy β”‚ 0.05 ms Β± 0.17 β”‚ 3.54 ms Β± 0.10 β”‚ 0.30 ms Β± 0.03  β”‚ 34.79 ms Β± 0.38 β”‚
β”‚  @fastify/busboy β”‚ 0.06 ms Β± 0.18 β”‚ 4.04 ms Β± 0.08 β”‚ 0.48 ms Β± 0.06  β”‚ 39.91 ms Β± 0.37 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

> @remix-run/multipart-parser@0.10.1 bench:deno /Users/michael/Projects/remix-the-web/packages/multipart-parser
> deno run --allow-sys ./bench/runner.ts

Platform: Darwin (24.5.0)
CPU: Apple M1 Pro
Date: 6/13/2025, 12:28:12 PM
Deno 2.3.6
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ (idx)            β”‚ 1 small file     β”‚ 1 large file       β”‚ 100 small files  β”‚ 5 large files       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ multipart-parser β”‚ "0.01 ms Β± 0.03" β”‚ "1.03 ms Β± 0.04"   β”‚ "0.05 ms Β± 0.01" β”‚ "10.05 ms Β± 0.20"   β”‚
β”‚ multipasta       β”‚ "0.02 ms Β± 0.07" β”‚ "1.04 ms Β± 0.03"   β”‚ "0.16 ms Β± 0.02" β”‚ "10.10 ms Β± 0.08"   β”‚
β”‚ busboy           β”‚ "0.05 ms Β± 0.19" β”‚ "3.06 ms Β± 0.15"   β”‚ "0.32 ms Β± 0.05" β”‚ "29.92 ms Β± 0.24"   β”‚
β”‚ @fastify/busboy  β”‚ "0.06 ms Β± 0.14" β”‚ "14.72 ms Β± 11.42" β”‚ "0.81 ms Β± 0.20" β”‚ "127.63 ms Β± 35.77" β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  • form-data-parser - Uses multipart-parser internally to parse multipart requests and generate FileUploads for storage
  • headers - Used internally to parse Content-Disposition and Content-Type metadata for each MultipartPart

Credits

Thanks to Jacob Ebey who gave me several code reviews on this project prior to publishing.

License

See LICENSE

Keywords

multipart

FAQs

Package last updated on 29 Apr 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts