Multipart upload
A "use server" action receives a Web Request with the multipart body; req.formData() parses it. Small bodies stay in-memory; bodies above the spool threshold (~1 MB) hit the streaming parser via the __bextReadChunk native bridge.
Tip
Don't forget enctype="multipart/form-data" on the form — without it the browser encodes fields as application/x-www-form-urlencoded and the file field is never sent. The spool threshold (~1 MB) applies to the entire body, not per file.
Upload a file
// src/actions/upload.ts
"use server";
export async function handleUpload(req: Request): Promise<Response> {
const form = await req.formData();
const file = form.get("file") as File | null;
if (!file) {
return new Response(null, { status: 303, headers: { Location: "/examples/upload" } });
}
// file.name, file.size, file.type are available here.
// For large files (>1 MB spool threshold) the body is streamed
// via __bextReadChunk — formData() handles this transparently.
return new Response(null, {
status: 303,
headers: {
Location: "/examples/upload?ok=1&name=" + encodeURIComponent(file.name)
+ "&size=" + file.size + "&type=" + encodeURIComponent(file.type),
},
});
}
// page.tsx — enctype="multipart/form-data" is required for file inputs
<form
method="post"
action="/_bext/action/handleUpload"
enctype="multipart/form-data"
>
<input type="file" name="file" />
<input name="note" placeholder="optional note" />
<button type="submit">upload</button>
</form>