leptos-rs/leptos · error

couldn't parse boundary

Error message

couldn't parse boundary

What it means

MultipartData::from_req extracts the boundary from the request's Content-Type header via multer::parse_boundary. If the header is missing, malformed, or lacks a valid boundary parameter, the .ok() yields None and .expect panics with 'couldn't parse boundary'. The library throws it because a multipart stream cannot be demarcated without a boundary.

Source

Thrown at server_fn/src/codec/multipart.rs:88

        Request::try_new_post_multipart(
            path,
            accepts,
            multi.into_client_data().unwrap(),
        )
    }
}

impl<E, T, Request> FromReq<MultipartFormData, Request, E> for T
where
    Request: Req<E> + Send + 'static,
    T: From<MultipartData>,
    E: FromServerFnError + Send + Sync,
{
    async fn from_req(req: Request) -> Result<Self, E> {
        let boundary = req
            .to_content_type()
            .and_then(|ct| multer::parse_boundary(ct).ok())
            .expect("couldn't parse boundary");
        let stream = req.try_into_stream()?;
        let data = multer::Multipart::new(
            stream.map(|data| data.map_err(|e| ServerFnErrorWrapper(E::de(e)))),
            boundary,
        );
        Ok(MultipartData::Server(data).into())
    }
}

View on GitHub (pinned to 32d20f6c9d)

Solutions

  1. Use the generated client (server_fn client) instead of a hand-rolled request so the boundary is generated automatically
  2. Ensure the request's Content-Type is multipart/form-data with a boundary parameter, e.g. multipart/form-data; boundary=xyz
  3. If calling manually with multer on the client side, pass the same boundary string used to build the body
  4. Inspect intermediate proxies/gateways for Content-Type header mutation

Example fix

// before
fetch("/api/upload", { method: "POST", body: formData }); // header may be dropped/mangled
// after
fetch("/api/upload", { method: "POST", body: formData }); // let browser set multipart/form-data; boundary=... (do NOT set Content-Type manually)
Defensive patterns

Strategy: validation

Validate before calling

fn has_valid_boundary(headers: &http::HeaderMap) -> bool {
    headers.get(http::header::CONTENT_TYPE)
        .and_then(|v| v.to_str().ok())
        .map(|ct| ct.starts_with("multipart/") && ct.contains("boundary="))
        .unwrap_or(false)
}

Try / catch

// panic, not Result: pre-validate Content-Type; if calling from wasm, wrap the whole
// request in catch_unwind is impractical — validate before send instead.
if !has_valid_boundary(&req.headers()) { return Err(ServerFnError::Request("missing multipart boundary".into())); }

Prevention

When it happens

Trigger: Calling a multipart server function (server_fn multipart codec) from a client that sends Content-Type: multipart/form-data without a boundary parameter, omits the Content-Type header entirely, or sends a different content type (e.g. application/json) to a multipart-encoding endpoint.

Common situations: Hand-rolled fetch/reqwest calls to a server fn that forgot to set multipart headers; proxies or CDNs stripping/rewriting the Content-Type header; client SDK built with a different codec than the server fn declares; tests posting raw bodies without a generated boundary.

Related errors


AI-assisted analysis of leptos-rs/leptos@32d20f6c9d (2026-09-01). Data as JSON: /api/errors/e7ee13d12870cd3e. Report an issue: GitHub.