Skip to content

form-data-parser

The form-data-parser package provides parseFormData, a drop-in replacement for request.formData() that streams file uploads instead of buffering them in memory. It returns a standard FormData object where file fields are represented as FileUpload instances.

Installation

The form data parser is included with Remix. No additional installation is needed.

Import

ts
import { parseFormData, FileUpload } from 'remix/form-data-parser'

API

parseFormData(request, options?, uploadHandler?)

Parses a multipart or URL-encoded request body and returns a FormData object. File fields are represented as FileUpload instances instead of File objects, giving you access to streaming and metadata.

ts
function parseFormData(
  request: Request,
  options?: ParseFormDataOptions,
  uploadHandler?: UploadHandler,
): Promise<FormData>

When called without an upload handler, files are buffered in memory:

ts
let formData = await parseFormData(request)

let title = formData.get('title') as string
let file = formData.get('avatar') as FileUpload

console.log(file.name) // 'photo.jpg'
console.log(file.type) // 'image/jpeg'
console.log(file.size) // 1048576

When called with an upload handler, files are streamed to the handler:

ts
let formData = await parseFormData(request, {}, async (fileUpload) => {
  // Stream the file to disk, S3, etc.
  let url = await uploadToS3(fileUpload)
  return url
})

let avatarUrl = formData.get('avatar') as string

FileUpload

A class representing an uploaded file. It extends the web File interface with additional metadata.

ts
class FileUpload {
  readonly fieldName: string  // Form field name
  readonly name: string       // Original filename
  readonly type: string       // MIME type
  readonly size: number       // Size in bytes

  arrayBuffer(): Promise<ArrayBuffer>
  text(): Promise<string>
  stream(): ReadableStream<Uint8Array>
}
ts
let file = formData.get('document') as FileUpload

console.log(file.fieldName) // 'document'
console.log(file.name)      // 'report.pdf'
console.log(file.type)      // 'application/pdf'
console.log(file.size)      // 2048576

// Read the file content
let bytes = await file.arrayBuffer()
let text = await file.text()
let stream = file.stream()

Upload Handler

An upload handler is a function that receives a FileUpload and returns a value to store in the FormData result. Use upload handlers to stream files to storage instead of buffering them in memory.

ts
type UploadHandler = (fileUpload: FileUpload) => Promise<any>

The value returned by the handler replaces the FileUpload in the resulting FormData. If the handler returns undefined, the field is omitted.

Options

OptionTypeDefaultDescription
maxFileSizenumberInfinityMaximum size in bytes for a single uploaded file.
maxTotalSizenumberInfinityMaximum total size in bytes across all uploaded files.
maxPartsnumberInfinityMaximum number of form fields (both text and file).
ts
let formData = await parseFormData(request, {
  maxFileSize: 5 * 1024 * 1024,   // 5 MB per file
  maxTotalSize: 20 * 1024 * 1024,  // 20 MB total
  maxParts: 20,
})

Examples

Basic File Upload

ts
import { parseFormData, FileUpload } from 'remix/form-data-parser'

router.map(uploadRoute, async ({ request }) => {
  let formData = await parseFormData(request)

  let title = formData.get('title') as string
  let cover = formData.get('cover') as FileUpload

  console.log(title)      // 'My Album'
  console.log(cover.name)  // 'photo.jpg'
  console.log(cover.type)  // 'image/jpeg'

  return new Response('OK')
})

Upload to Filesystem

ts
import { parseFormData } from 'remix/form-data-parser'
import { writeFile } from 'remix/fs'

let formData = await parseFormData(request, {}, async (fileUpload) => {
  let path = `./uploads/${fileUpload.name}`
  await writeFile(path, fileUpload)
  return path
})

let savedPath = formData.get('avatar') as string
// './uploads/photo.jpg'

Upload to S3

ts
import { parseFormData } from 'remix/form-data-parser'
import { createS3FileStorage } from 'remix/file-storage-s3'

let s3 = createS3FileStorage({
  bucket: 'my-uploads',
  region: 'us-east-1',
})

let formData = await parseFormData(request, {}, async (fileUpload) => {
  let key = `uploads/${Date.now()}-${fileUpload.name}`
  await s3.set(key, fileUpload)
  return key
})

Multiple Files

ts
let formData = await parseFormData(request)

let photos = formData.getAll('photos') as FileUpload[]

for (let photo of photos) {
  console.log(photo.name, photo.size)
}

Filtering by Field Name

Only process specific file fields in the upload handler:

ts
let formData = await parseFormData(request, {}, async (fileUpload) => {
  if (fileUpload.fieldName === 'avatar') {
    return await saveAvatar(fileUpload)
  }
  // Return undefined to skip other file fields
})

Released under the MIT License.