remix-run/remix · error · MultipartParseError

Invalid Content-Type header: missing boundary

Error message

Invalid Content-Type header: missing boundary

What it means

The request's Content-Type is multipart, but the header lacks the boundary parameter required to delimit parts. Without a boundary the multipart body cannot be split, so the parser throws MultipartParseError immediately after extracting the header.

Source

Thrown at packages/multipart-parser/src/lib/multipart-request.ts:50

 * @param options Optional parser options, such as `maxHeaderSize`, `maxFileSize`, `maxParts`,
 * and `maxTotalSize`
 * @yields Parsed {@link MultipartPart} objects from the request body
 * @returns An async generator yielding {@link MultipartPart} objects
 */
export async function* parseMultipartRequest(
  request: Request,
  options?: MultipartParserOptions,
): AsyncGenerator<MultipartPart, void, unknown> {
  if (!isMultipartRequest(request)) {
    throw new MultipartParseError('Request is not a multipart request')
  }
  if (!request.body) {
    throw new MultipartParseError('Request body is empty')
  }

  let boundary = getMultipartBoundary(request.headers.get('Content-Type')!)
  if (!boundary) {
    throw new MultipartParseError('Invalid Content-Type header: missing boundary')
  }

  yield* parseMultipartStream(request.body, {
    boundary,
    maxHeaderSize: options?.maxHeaderSize,
    maxFileSize: options?.maxFileSize,
    maxParts: options?.maxParts,
    maxTotalSize: options?.maxTotalSize,
  })
}

View on GitHub (pinned to 9696913134)

Solutions

  1. Do not set the Content-Type manually when sending FormData — let fetch/the browser generate it with the boundary
  2. If constructing manually, append a boundary: multipart/form-data; boundary=----formdata-xyz and use the same boundary in the body
  3. Verify intermediate proxies/load balancers pass the full Content-Type through unchanged

Example fix

// before
await fetch(url, {
  method: 'POST',
  headers: { 'Content-Type': 'multipart/form-data' }, // no boundary
  body: formData,
})

// after
await fetch(url, {
  method: 'POST',
  body: formData, // fetch sets Content-Type with boundary automatically
})
Defensive patterns

Strategy: validation

Validate before calling

let contentType = request.headers.get('Content-Type') ?? ''
if (!/boundary=/i.test(contentType)) {
  return new Response('Missing multipart boundary', { status: 400 })
}

Try / catch

try {
  for await (let part of parseMultipartRequest(request)) { /* ... */ }
} catch (error) {
  if (error instanceof MultipartParseError && error.message.includes('missing boundary')) {
    return new Response('Content-Type must include a boundary', { status: 400 })
  }
  throw error
}

Prevention

When it happens

Trigger: Content-Type: multipart/form-data with no ; boundary=... parameter — typically from hand-built headers, some proxies/CDNs that rewrite the header, or manually constructed Requests where the content type was set without a boundary.

Common situations: Setting headers: { 'Content-Type': 'multipart/form-data' } manually in fetch (the most common mistake — omitting boundary); API gateways normalizing or truncating the Content-Type; copying content types from logs into test fixtures.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/a6ce1dab32277331. Report an issue: GitHub.