remix-run/remix · error · MultipartParseError

Invalid multipart stream: missing initial boundary

Error message

Invalid multipart stream: missing initial boundary

What it means

The first bytes of a multipart body must be the opening delimiter (--boundary). If the parser's first chunk doesn't begin with the expected opening boundary, it throws MultipartParseError('Invalid multipart stream: missing initial boundary'), because nothing in the body can be reliably located otherwise.

Source

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

        this.#currentHeader = chunk.subarray(index, headerEndIndex)
        this.#currentContent = []
        this.#contentLength = 0

        index = headerEndIndex + 4 // Skip header + \r\n\r\n

        this.#state = MultipartParserStateBody

        continue
      }

      if (this.#state === MultipartParserStateStart) {
        if (chunkLength < this.#openingBoundaryLength) {
          this.#buffer = chunk
          break
        }

        if (this.#findOpeningBoundary(chunk) !== 0) {
          throw new MultipartParseError('Invalid multipart stream: missing initial boundary')
        }

        index = this.#openingBoundaryLength

        this.#state = MultipartParserStateAfterBoundary
      }
    }
  }

  #append(chunk: Uint8Array): void {
    if (chunk.length === 0) {
      return
    }

    if (this.#contentLength + chunk.length > this.maxFileSize) {
      throw new MaxFileSizeExceededError(this.maxFileSize)
    }

View on GitHub (pinned to 9696913134)

Solutions

  1. Verify the boundary in the Content-Type exactly matches the --boundary delimiters in the raw body (dump the first ~100 bytes)
  2. Ensure the Content-Type header is generated by the same FormData/client that built the body — never mix manual headers with generated bodies
  3. Remove any preamble before the first --boundary if you control the sender

Example fix

// before
headers: { 'Content-Type': `multipart/form-data; boundary=${myBoundary}` }
body: `--${otherBoundary}\r\n...` // mismatch throws

// after
let body = `--${myBoundary}\r\nContent-Disposition: form-data; name="f"\r\n\r\nv\r\n--${myBoundary}--\r\n`
headers: { 'Content-Type': `multipart/form-data; boundary=${myBoundary}` }
Defensive patterns

Strategy: try-catch

Validate before calling

// sanity-check the first bytes before parsing (when body is buffered)
let head = new TextDecoder().decode(bodyBytes.subarray(0, boundary.length + 4))
if (!head.startsWith(`--${boundary}`)) {
  throw new Response('Body does not start with multipart boundary', { status: 400 })
}

Try / catch

try {
  for await (let part of parseMultipartStream(stream, { boundary })) { /* ... */ }
} catch (error) {
  if (error instanceof MultipartParseError && error.message.includes('missing initial boundary')) {
    return new Response('Malformed multipart body', { status: 400 })
  }
  throw error
}

Prevention

When it happens

Trigger: A body that starts with a preamble or garbage instead of --boundary; a boundary extracted from the Content-Type that doesn't match the actual body delimiters (e.g. extra quotes, case differences, or URL-encoding); truncated or restarted uploads.

Common situations: Content-Type boundary value not matching the body (proxy mangling, manual header construction), clients sending a nonstandard preamble, or a partially-consumed body stream being reparsed.

Related errors


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