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
- Use the generated client (server_fn client) instead of a hand-rolled request so the boundary is generated automatically
- Ensure the request's Content-Type is multipart/form-data with a boundary parameter, e.g. multipart/form-data; boundary=xyz
- If calling manually with multer on the client side, pass the same boundary string used to build the body
- 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
- Never set Content-Type manually for FormData — let the HTTP client generate boundary
- Use the generated server-fn client rather than hand-built requests
- Check that proxies/CDNs don't rewrite Content-Type
- Add an integration test posting real multipart bodies
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
- Executor::spawn called, but no global 'spawn' function is co
- Executor::spawn_local called, but no global 'spawn_local' fu
- At {caller}, tried to spawn a Future with Executor::spawn()
- At {caller}, tried to spawn a Future with Executor::spawn_lo
- called `unwrap_{variant}()` on a non-`{variant}` variant of
AI-assisted analysis of leptos-rs/leptos@32d20f6c9d (2026-09-01).
Data as JSON: /api/errors/e7ee13d12870cd3e.
Report an issue: GitHub.