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
- Do not set the Content-Type manually when sending FormData — let fetch/the browser generate it with the boundary
- If constructing manually, append a boundary: multipart/form-data; boundary=----formdata-xyz and use the same boundary in the body
- 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
- Never hardcode 'Content-Type: multipart/form-data' — let FormData/fetch set it
- If constructing headers manually, always append the boundary you actually delimit the body with
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
- Request is not a multipart request
- Request is not a multipart request
- Invalid Content-Type header: missing boundary
- Request body is empty
- Multipart boundary exceeds maximum length of ${maxBoundaryLe
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/a6ce1dab32277331.
Report an issue: GitHub.