> ## Documentation Index
> Fetch the complete documentation index at: https://dev-docs.persona.hasanraiyan.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Uploads

> How @personaai/express handles multipart file uploads — native FormData parsing, multer coexistence, and request body size limits.

Two routes accept file uploads, and the adapter parses them **natively** — zero extra
dependencies:

* `POST /files` — single-file upload, `file` part required (plus optional `agentId`/`threadId` form fields)
* `POST /knowledge/:id/documents` — one or more `files` parts (plus optional `agentId`/`threadId`)

Both are *always-on* (`/files`) or *capability-gated* (`/knowledge` — enable `capabilities.knowledge`)
routes of the runtime; the adapter's job is only to translate the incoming multipart body into the
runtime's `file`/`files`/`body` fields.

## Native parsing

When a multipart request arrives, the adapter reads the raw stream and parses it with Node's
built-in `FormData` (undici, shipped with Node 18+) — no `multer`, `busboy`, or `formidable`
required:

```bash theme={null}
curl -X POST http://localhost:3000/api/persona/files \
  -H "Authorization: Bearer <credential>" \
  -F "file=@resume.pdf;type=application/pdf" \
  -F "agentId=ag_123"
```

The `file` part becomes `RuntimeRequest.file` (`{filename, content, contentType}`); any other form
fields (`agentId`, `threadId`) land on `RuntimeRequest.body` and flow through to the SDK upload
call.

## Using multer yourself

The adapter coexists with `multer`. If your own middleware already parsed the multipart body, the
adapter detects the parsed `req.file`/`req.files` and **honors them** instead of reading the stream
again:

```ts theme={null}
import multer from 'multer';

const upload = multer({ storage: multer.memoryStorage() });

// multer parses first; the adapter uses its output
app.post('/api/persona/files', upload.single('file'), (req, res, next) => {
  // the adapter is mounted below and sees req.file already populated
  next();
});
app.use('/api/persona', persona.router);
```

**One warning:** if a body parser ran *without* capturing the file (e.g. `express.json()` on a
multipart request, or a multer instance configured to discard), the adapter detects that the
stream was already consumed and responds `400 INVALID_REQUEST` — it can't reconstruct the body.
Keep multer on the same route or path prefix as the adapter mount, and don't put a generic body
parser in front of multipart routes.

## Body size limits

Like the runtime's own reference bridge, the adapter buffers request bodies (JSON *and* multipart)
in memory **with no size limit of its own**. Enforce one with your own middleware when you need it:

```ts theme={null}
app.use(express.json({ limit: '10mb' })); // caps JSON bodies before they reach the adapter
app.use('/api/persona', persona.router);
```

For multipart specifically, either let multer enforce its limits (it returns `413` on
`limits.fileSize` overflow) or add an `express.raw({ type: 'multipart/form-data', limit: ... })`
guard ahead of the mount. Choose limits that match your product — typical chat attachments are
1–25 MB.

## Downloads are binary

The mirror image: `GET /files/:id` streams the file back through as raw bytes
(`kind: 'binary'`), chunk by chunk, with the correct `Content-Type`. The adapter handles
`Content-Disposition`-style headers from the runtime verbatim.
