
Product
Socket for Jira Is Now Available
Socket for Jira lets teams turn alerts into Jira tickets with manual creation, automated ticketing rules, and two-way sync.
nestjs-form-data
Advanced tools
NestJS middleware for handling multipart/form-data, which is primarily used for uploading files
An object-oriented approach to handling multipart/form-data in NestJS. Uploaded files become typed class instances with built-in validation, automatic cleanup, and pluggable storage — no manual stream wiring required.
NestJS ships with Multer-based file upload, but it works outside the DTO validation flow — you handle files through @UploadedFile() separately from @Body(), losing the single-source-of-truth that DTOs provide.
nestjs-form-data takes a different approach: files are first-class properties on your DTO. They arrive as typed objects (MemoryStoredFile, FileSystemStoredFile, or your own custom class), validated with the same decorators you already use for strings and numbers:
export class CreatePostDto {
@IsString()
title: string;
@IsFile()
@MaxFileSize(5e6)
@HasMimeType(['image/jpeg', 'image/png'])
cover: MemoryStoredFile;
}
No @UploadedFile(), no separate pipes, no manual cleanup. Just a DTO.
StoredFile class with properties like size, mimeType, extension, originalName, and a reliable buffer or path{ each: true })photos[0][name]) are parsed into proper nested structuresMemoryStoredFile for speed, FileSystemStoredFile for large files, or extend StoredFile to write your own (S3, GCS, etc.)npm install nestjs-form-data
This module requires class-validator and class-transformer as peer dependencies:
npm install class-validator class-transformer
Register a global validation pipe in main.ts:
import { ValidationPipe } from '@nestjs/common';
app.useGlobalPipes(
new ValidationPipe({
transform: true, // recommended to avoid issues with file array transformations
}),
);
Add the module to your application:
import { NestjsFormDataModule } from 'nestjs-form-data';
@Module({
imports: [NestjsFormDataModule],
})
export class AppModule {}
Apply @FormDataRequest() to your controller method and define a DTO:
import { Controller, Post, Body } from '@nestjs/common';
import { FormDataRequest, MemoryStoredFile, IsFile, MaxFileSize, HasMimeType } from 'nestjs-form-data';
class UploadAvatarDto {
@IsFile()
@MaxFileSize(1e6)
@HasMimeType(['image/jpeg', 'image/png'])
avatar: MemoryStoredFile;
}
@Controller('users')
export class UsersController {
@Post('avatar')
@FormDataRequest()
uploadAvatar(@Body() dto: UploadAvatarDto) {
// dto.avatar is a MemoryStoredFile instance
console.log(dto.avatar.originalName); // "photo.jpg"
console.log(dto.avatar.size); // 94521
console.log(dto.avatar.mimeType); // "image/jpeg"
console.log(dto.avatar.buffer); // <Buffer ff d8 ff ...>
}
}
That's it. The file is parsed, validated, and available as a typed object on your DTO.
Install @fastify/multipart and register it:
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import multipart from '@fastify/multipart';
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter());
app.register(multipart);
await app.listen(3000);
}
Everything else works the same — DTOs, decorators, and storage types are platform-agnostic.
avatar: MemoryStoredFile;
The file is loaded entirely into RAM as a Buffer. Fast, but not suitable for large files.
avatar: FileSystemStoredFile;
The file is written to a temporary directory on disk and available via file.path during request processing. Automatically deleted when the request completes.
Extend the StoredFile abstract class to implement your own storage (e.g., stream directly to S3):
import { StoredFile } from 'nestjs-form-data';
export class S3StoredFile extends StoredFile {
s3Key: string;
size: number;
static async create(meta, stream, config): Promise<S3StoredFile> {
// upload stream to S3, return instance
}
async delete(): Promise<void> {
// delete from S3
}
}
Then use it: @FormDataRequest({ storage: S3StoredFile })
@Module({
imports: [
NestjsFormDataModule.config({
storage: MemoryStoredFile,
isGlobal: true,
limits: {
fileSize: 5e6, // 5 MB
files: 10,
},
}),
],
})
export class AppModule {}
NestjsFormDataModule.configAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
storage: MemoryStoredFile,
limits: {
files: configService.get<number>('MAX_FILES'),
},
}),
inject: [ConfigService],
});
You can also use useClass or useExisting patterns — see NestJS async providers for details:
// useClass — creates a new instance
NestjsFormDataModule.configAsync({
useClass: MyFormDataConfigService,
});
// useExisting — reuses an imported provider
NestjsFormDataModule.configAsync({
imports: [MyConfigModule],
useExisting: MyFormDataConfigService,
});
Where the config service implements:
export class MyFormDataConfigService implements NestjsFormDataConfigFactory {
configAsync(): FormDataInterceptorConfig {
return {
storage: FileSystemStoredFile,
fileSystemStoragePath: '/tmp/nestjs-fd',
};
}
}
Override global config for a specific endpoint:
@Post('upload')
@FormDataRequest({ storage: FileSystemStoredFile })
upload(@Body() dto: UploadDto) {}
| Option | Type | Default | Description |
|---|---|---|---|
storage | Type<StoredFile> | MemoryStoredFile | Storage class for uploaded files |
isGlobal | boolean | false | Make the module available to all submodules |
fileSystemStoragePath | string | /tmp/nestjs-tmp-storage | Temp directory for FileSystemStoredFile |
cleanupAfterSuccessHandle | boolean | true | Delete files after successful request |
cleanupAfterFailedHandle | boolean | true | Delete files after failed request |
awaitCleanup | boolean | true | Wait for file cleanup before sending response. Set to false for faster responses (cleanup runs in the background) |
limits | object | {} | Busboy limits: fileSize, files, fields, parts, headerPairs |
All validators work with { each: true } for arrays of files.
Checks if the value is an uploaded file (instance of StoredFile).
@IsFile()
avatar: MemoryStoredFile;
@IsFiles()
photos: MemoryStoredFile[];
File size constraints in bytes.
@MaxFileSize(5e6) // max 5 MB
@MinFileSize(1024) // min 1 KB
avatar: MemoryStoredFile;
Validate MIME type. Supports exact strings, wildcard patterns, and regular expressions.
// exact match
@HasMimeType(['image/jpeg', 'image/png'])
// wildcard — matches any image type
@HasMimeType('image/*')
// regex
@HasMimeType([/image\/.*/])
MIME type detection priority:
To enforce that the MIME type comes from a specific source:
import { MetaSource } from 'nestjs-form-data';
@HasMimeType(['image/jpeg'], MetaSource.bufferMagicNumber) // only trust magic number
Access the source at runtime via file.mimeTypeWithSource.
Validate file extension. Same source priority and strict mode as @HasMimeType.
@HasExtension(['jpg', 'png'])
// strict — only trust extension from magic number detection
@HasExtension(['jpg'], MetaSource.bufferMagicNumber)
Access the source at runtime via file.extensionWithSource.
Controller:
import { FileSystemStoredFile, FormDataRequest } from 'nestjs-form-data';
@Controller()
export class NestjsFormDataController {
@Post('load')
@FormDataRequest({ storage: FileSystemStoredFile })
getHello(@Body() testDto: FormDataTestDto): void {
console.log(testDto);
}
}
DTO:
import { FileSystemStoredFile, HasMimeType, IsFile, MaxFileSize } from 'nestjs-form-data';
export class FormDataTestDto {
@IsFile()
@MaxFileSize(1e6)
@HasMimeType(['image/jpeg', 'image/png'])
avatar: FileSystemStoredFile;
}
Send request (via Insomnia):

DTO:
import { FileSystemStoredFile, HasMimeType, IsFiles, MaxFileSize } from 'nestjs-form-data';
export class FormDataTestDto {
@IsFiles()
@MaxFileSize(1e6, { each: true })
@HasMimeType(['image/jpeg', 'image/png'], { each: true })
avatars: FileSystemStoredFile[];
}
Send request (via Insomnia):

export class CreateProductDto {
@IsString()
name: string;
@IsNumber()
@Type(() => Number)
price: number;
@IsFile()
@MaxFileSize(5e6)
@HasMimeType(['image/*'])
image: MemoryStoredFile;
}
See CHANGELOG.md.
FAQs
NestJS middleware for handling multipart/form-data, which is primarily used for uploading files
The npm package nestjs-form-data receives a total of 24,856 weekly downloads. As such, nestjs-form-data popularity was classified as popular.
We found that nestjs-form-data demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket for Jira lets teams turn alerts into Jira tickets with manual creation, automated ticketing rules, and two-way sync.

Company News
Socket won two 2026 Reppy Awards from RepVue, ranking in the top 5% of all sales orgs. AE Alexandra Lister shares what it's like to grow a sales career here.

Security News
NIST will stop enriching most CVEs under a new risk-based model, narrowing the NVD's scope as vulnerability submissions continue to surge.