Skip to Content

@TypedFormData

@nestia/core
export namespace TypedFormData { export function Body<Multer extends IMulterBase>( factory: () => Multer | Promise<Multer>, ): ParameterDecorator; export type IMulterBase = ExpressMulter.Multer | FastifyMulter.Multer; }

Parameter decorator for multipart/form-data request bodies — the content type browsers send when an HTML form contains a <input type="file">. Use it for any endpoint that accepts file uploads alongside other fields.

Replaces the NestJS combination of @UploadedFile() / @UploadedFiles() / FileFieldsInterceptor / @ApiConsumes("multipart/form-data") / @ApiBody({...}) with a single decorator that reads everything from the parameter’s TypeScript type.

Only @TypedFormData.Body() is supported by @nestia/sdk’s generators. Vanilla @UploadedFile() routes don’t appear in the generated SDK or Swagger document.


Basic usage

src/controllers/ArticlesController.ts
import { TypedFormData, TypedRoute } from "@nestia/core"; import { Controller } from "@nestjs/common"; import Multer from "multer"; interface IArticleCreate { title: string; body: string | null; thumbnail?: File; attachments: File[]; tags: string[]; } @Controller("articles") export class ArticlesController { @TypedRoute.Post() public async create( @TypedFormData.Body(() => Multer()) input: IArticleCreate, ): Promise<{ id: string }> { // input.thumbnail is a Blob | undefined // input.attachments is Blob[] // input.title / body / tags are validated strings / arrays return { id: "..." }; } }

Multipart fields with a binary file (browser sends Content-Disposition: form-data; name="…"; filename="…") become File (a browser-compatible Blob subclass). Plain fields become their declared TypeScript type with the same coercion rules as @TypedQuery.


What gets parsed

Declared typeWire payloadYou receive
Fileone file fieldFile
File[]repeated file fieldsFile[]
File?optional fileFile or undefined
stringplain text field"..."
numberplain text fieldcoerced to number (or 400)
booleanplain text fieldcoerced to boolean (or 400)
string[]repeated plain fieldsstring[]
Tagged typesas above + validationvalidated value (or 400)

Constraints via tags.* work the same as on JSON bodies — string & tags.MinLength<3>, number & tags.Maximum<100>, etc.


File handling

The File you receive is a Blob. To read it as a buffer:

import { TypedFormData, TypedRoute } from "@nestia/core"; @TypedRoute.Post() public async create( @TypedFormData.Body(() => Multer()) input: { upload: File }, ): Promise<void> { const buf = Buffer.from(await input.upload.arrayBuffer()); // upload to S3, save to disk, etc. }

input.upload.name is the filename, input.upload.type the MIME type, input.upload.size the byte length.


Multer configuration

The factory argument is a function that returns a configured Multer instance. You control storage, file-size limits, and filtering through Multer’s standard options:

import Multer from "multer"; @TypedRoute.Post() public async upload( @TypedFormData.Body(() => Multer({ storage: Multer.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024 }, // 10 MB })) input: IUpload, ): Promise<void> { ... }

On Fastify, use @fastify/multipart instead:

import FastifyMultipart from "@fastify/multipart"; @TypedRoute.Post() public async upload( @TypedFormData.Body(() => FastifyMultipart()) input: IUpload, ): Promise<void> { ... }

Comparison with vanilla NestJS

// Vanilla NestJS — separate decorators for files vs fields, separate Swagger schema @Post() @ApiConsumes("multipart/form-data") @UseInterceptors( FileFieldsInterceptor([ { name: "thumbnail", maxCount: 1 }, { name: "attachments" }, ]), ) @ApiBody({ schema: { type: "object", properties: { title: { type: "string" }, body: { type: "string", nullable: true }, thumbnail: { type: "string", format: "binary" }, attachments: { type: "array", items: { type: "string", format: "binary" } }, }, }, }) async create( @Body() body: any, @UploadedFiles() files: { thumbnail?: Express.Multer.File[]; attachments?: Express.Multer.File[] }, ) { ... }

Versus the Nestia version above — one decorator, no Swagger schema, no UseInterceptors, no array-of-array unwrapping. The TypeScript type is the contract.


See also

  • TypedBody — JSON bodies (when no files are involved).
  • TypedQuery.Body — form-urlencoded bodies (text fields only).
  • SDK — how @TypedFormData routes appear in the generated client.
Last updated on