remix-run/remix · error · MultipartParseError
Request body is empty
Error message
Request body is empty
What it means
parseMultipartRequest throws MultipartParseError('Request body is empty') when the Request has a null/absent body. Even a correctly-typed multipart request must carry a body stream for the parser to consume, so an empty body is rejected up front rather than producing a confusing parse failure later.
Source
Thrown at packages/multipart-parser/src/lib/multipart-request.ts:45
* Parse a multipart [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
* and yield each part as a {@link MultipartPart} object. Useful in HTTP server contexts
* for handling incoming `multipart/*` requests.
*
* @param request The `Request` object containing multipart data
* @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
- Check request.body is non-null before parsing
- Avoid consuming the body twice — remove prior request.formData()/request.text() calls or clone the request first
- Ensure clients actually send a multipart body even for empty forms (FormData with at least one field)
Example fix
// before
let parts = parseMultipartRequest(request) // body already consumed
// after
if (!request.body) {
return new Response('Empty body', { status: 400 })
}
let parts = parseMultipartRequest(request.clone()) // preserve original for later reads Defensive patterns
Strategy: validation
Validate before calling
if (request.method !== 'POST' && request.method !== 'PUT') {
return new Response('Method not allowed', { status: 405 })
}
if (!request.body) {
return new Response('Empty body', { status: 400 })
} Try / catch
try {
for await (let part of parseMultipartRequest(request)) { /* ... */ }
} catch (error) {
if (error instanceof MultipartParseError && error.message === 'Request body is empty') {
return new Response('Empty request body', { status: 400 })
}
throw error
} Prevention
- Never read request.body/formData before multipart parsing; clone first if you must
- Reject bodyless methods (GET/HEAD) at the route level before parsing
When it happens
Trigger: Calling parseMultipartRequest on a Request constructed with no body (or body: null), a GET/HEAD request, or a body already consumed or cancelled by earlier middleware/handler code.
Common situations: Requests whose stream was already read by another parser (e.g. await request.formData() earlier in the handler), hand-constructed Request objects in tests without a body, or intermediaries that buffer and drop the body.
Related errors
- Request is not a multipart request
- Invalid Content-Type header: missing boundary
- Request is not a multipart request
- Invalid Content-Type header: missing boundary
- Multipart boundary exceeds maximum length of ${maxBoundaryLe
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/5098eb305bae3967.
Report an issue: GitHub.