remix-run/remix · error · MultipartParseError

Invalid Content-Type header: missing boundary

Error message

Invalid Content-Type header: missing boundary

What it means

The Node parseMultipartRequest extracts the boundary from req.headers['content-type'] and throws MultipartParseError when the multipart content type has no boundary parameter. The boundary is mandatory metadata for splitting parts, so its absence is fatal.

Solutions

  1. Send multipart bodies with FormData / curl -F so the boundary is generated automatically
  2. If setting the header manually, include the boundary and delimit the body with it
  3. Log and inspect req.headers['content-type'] at the server edge to catch rewrites

Example fix

# before
curl -X POST http://localhost:3000/upload \
  -H 'Content-Type: multipart/form-data' \
  -d 'file=abc'

# after
curl -X POST http://localhost:3000/upload \
  -F 'file=@./photo.png'
Defensive patterns

Strategy: validation

Validate before calling

let contentType = String(req.headers['content-type'] ?? '')
if (!/boundary=/i.test(contentType)) {
  res.writeHead(400).end('Missing multipart boundary')
  return
}

Try / catch

try {
  for await (let part of parseMultipartRequest(req)) { /* ... */ }
} catch (error) {
  if (error instanceof MultipartParseError && error.message.includes('missing boundary')) {
    res.writeHead(400).end('Content-Type must include a boundary')
    return
  }
  throw error
}

Prevention

When it happens

Trigger: A raw Node request with Content-Type: multipart/form-data but no boundary=... — typically because a client or upstream proxy set the header manually or stripped the parameter.

Common situations: curl invocations with -H 'Content-Type: multipart/form-data' but using -d (urlencoded) instead of -F; proxies that rewrite content-type; misconfigured SDK uploads.

Related errors


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

Appendix: source

Thrown at packages/multipart-parser/src/lib/multipart.node.ts:79

 * Parse a multipart Node.js request and yield each part as a {@link MultipartPart} object.
 *
 * @param req The Node.js `http.IncomingMessage` object containing multipart data
 * @param options Options for the parser, such as `maxHeaderSize`, `maxFileSize`, `maxParts`,
 * and `maxTotalSize`
 * @yields Parsed {@link MultipartPart} objects from the multipart request body
 * @returns An async generator yielding {@link MultipartPart} objects
 */
export async function* parseMultipartRequest(
  req: http.IncomingMessage,
  options?: MultipartParserOptions,
): AsyncGenerator<MultipartPart, void, unknown> {
  if (!isMultipartRequest(req)) {
    throw new MultipartParseError('Request is not a multipart request')
  }

  let boundary = getMultipartBoundary(req.headers['content-type']!)
  if (!boundary) {
    throw new MultipartParseError('Invalid Content-Type header: missing boundary')
  }

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

View on GitHub (pinned to 9696913134)