Request.formData() — zero extra dependencies, identical on the Node.js and Edge runtimes:
POST /files— single-file upload,filepart required (plus optionalagentId/threadIdform fields)POST /knowledge/:id/documents— one or morefilesparts (plus optionalagentId/threadId)
/files is always on; /knowledge is capability-gated (enable capabilities.knowledge — see
Runtime capabilities). The adapter’s job is only to translate the
incoming multipart body into the runtime’s file / files / body fields.
Native parsing
There is nomulter, busboy, or formidable in this path, and nothing to configure — the
adapter calls request.formData() on the request Next handed it:
Do not set a
Content-Type header yourself when posting a FormData — the browser adds the
multipart boundary, and overriding it makes the body unparsable (which the adapter answers with
400 INVALID_REQUEST).
The same shape works from curl:
Uploading from a Server Action
A Server Action can forward its ownFormData straight through, since the route is same-origin —
but it must carry the session forward. Uploading from the client (where the cookie is sent
automatically) is the simpler path; reach for a Server Action only when you need to validate or
rewrite the form first.
Body size limits
The adapter buffers request bodies (JSON and multipart) in memory with no size limit of its own, and App Router route handlers have no built-in limit either — the Pages Router’sapi.bodyParser.sizeLimit config does not apply here. Two things to know:
- Your host probably has one. Vercel caps a serverless function’s request body at 4.5 MB; other platforms and any reverse proxy in front of your app have their own ceilings. A file larger than that never reaches your route handler at all.
- Enforce your own limit if you need one. The cheapest place is
middleware.ts, which runs before the body is ever read:
middleware.ts
onFileUpload hook is not an enforcement point: it fires after a successful
upload and receives only { userId, fileName, mimeType }. Use it for auditing and metrics, not
for rejection — see the Runtime Hooks reference.
Downloads are binary
The mirror image:GET /files/:id streams the file back as raw bytes (kind: 'binary'), chunk by
chunk through a ReadableStream, with the runtime’s Content-Type and content-disposition
headers forwarded verbatim. Nothing is buffered in memory, so a large download costs the same as a
small one.