Skip to content

File uploads

form() is a first-party body codec for multipart/form-data: bounded by the body budget (the raw body is refused at the cap before parsing), parsed by the platform’s FormData implementation (Lugas ships no MIME parser), and delivered to handlers as plain maps with native File values. See ADR-0030.

import { form, json, route } from "lugas";
route({
body: form({
maxFields: 32, // text parts (default 64)
maxFiles: 4, // file parts (default 16)
maxFileSize: 5 * 1024 * 1024, // per-file bytes (default 10 MiB)
}),
handler: (ctx) => {
ctx.body.fields; // Record<string, string> — text parts
ctx.body.files; // Record<string, File> — native File: .name .type .size .stream() .arrayBuffer()
return json(201, { saved: Object.keys(ctx.body.files) });
},
});

body: form(...) occupies the ordinary body slot: the manifest records the body capability, ctx.body is typed from the descriptor, and every framework error path applies (Problem Details, redaction, logging, CORS, secure headers).

The battery’s point: uploads are the heaviest request class, so they are held to the body budget like every framework-parsed body:

  1. A Content-Length above the effective budget is refused before reading413 BODY_BUDGET_EXCEEDED.
  2. Otherwise the body is read stream-wise; crossing the cap aborts the read — 413, never a partial parse.
  3. Only bytes within the cap ever reach a parser.

The effective budget is the usual composition: app bodyBudget default, per-route budget override, serve({ maxRequestBodySize }) ceiling that always wins.

Config Default Exceeded
maxFields 64 413 FORM_LIMIT_EXCEEDED
maxFiles 16 413 FORM_LIMIT_EXCEEDED
maxFileSize 10 MiB (per file) 413 FORM_LIMIT_EXCEEDED

Limits are positive integers validated at form() creation (LUGAS_FORM_001 on violations, including unknown keys). Byte overruns and shape overruns are both 413 — “too large” — with distinct code values (BODY_BUDGET_EXCEEDED vs FORM_LIMIT_EXCEEDED) so clients can tell which bound fired.

Condition Status Code
Non-multipart content type (or missing boundary) 415 UNSUPPORTED_MEDIA_TYPE
Unparseable multipart body 400 MALFORMED_MULTIPART
Byte budget exceeded (header or stream) 413 BODY_BUDGET_EXCEEDED
Any form limit exceeded 413 FORM_LIMIT_EXCEEDED

None of these reach the handler — handlers never see half-parsed bodies.

  • Repeated part names collapse last-wins, mirroring parseCookies. The native request.formData() remains available in handlers when you genuinely need multiplicity.
  • Files are native Filestream() forwards to storage/another service without re-serializing; arrayBuffer() for small files.
  • No disk-spooling config, no streaming part events (ADR-0030 non-goals): handlers get the complete bounded body; spooling for large caps is platform behavior.
  • Body limits — budgets, ceilings, and the 413 boundary.
  • Validation — Standard Schema slots for the JSON lanes (compose: validate ctx.body.fields in the handler, or parse uploads to JSON first).
  • DiagnosticsLUGAS_FORM_001 and the failure codes above.