[ ] Regression
[ ] Bug report
[ ] Feature request
[ X ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.
I'm using nestjs/swagger and trying to figure out a way to upload a file and also pass additional form data along with the image.
I'm using this to upload the image, and it works fine.
@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@ApiImplicitFile({ name: 'file', required: true })
uploadFile(@UploadedFile() file) {
console.log('got upload');
console.log('...file', file);
}
However, I'd like to include some meta data about my file as form data. I tried using @Body(), but that expects a json payload, and the upload requires multipart/form-data.
@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@ApiImplicitFile({ name: 'file', required: true })
uploadFile(@UploadedFile() file, @Body() body: MyDto) {
console.log('got upload');
console.log('...file', file);
console.log('...body', body);
}
I also tried ApiImplicitBody, but couldn't seem to get it to work. I'm just guessing because I seem to be deep in undocumented territory.
What is the correct way to:
I experience a similar problem. The file upload does work for me using multipart/form-data, and I am successfully sending additional properties with it:
@Post()
@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@ApiImplicitFile({ name: 'file', required: true })
async uploadModel(@UploadedFile() file, @Body() modelData: CreateModelDto) {
// (...)
}
All additional properties are passed to the modelData when sending a proper POST request from the application.
The issue is that swagger does not understand it and the "Try it out" does not work. It sends just the file without any additional data.


I made a pull request addressing this issue:
https://github.com/nestjs/swagger/pull/168
Fixed in 3.1.0
i have been reading on @nestjs/swagger version 3.1.0
and it no support additional form data,
if you want genarate document for this you need to write a custom decorator like this.
api-implicit-form-data.decorator.ts
import { createParamDecorator } from '@nestjs/swagger/dist/decorators/helpers';
import { isNil } from 'lodash';
const initialMetadata = {
name: '',
required: true,
};
export const ApiImplicitFormData = (metadata: {
name: string;
description?: string;
required?: boolean;
type: any;
}): MethodDecorator => {
const param = {
name: isNil(metadata.name) ? initialMetadata.name : metadata.name,
in: 'formData',
description: metadata.description || '',
required: metadata.required || false,
type: metadata.type,
};
return createParamDecorator(param, initialMetadata);
};
and using it ad controller function like this:
test.controller.ts
@ApiOperation({ title: 'Upload Photo Campaign' })
@ApiConsumes('multipart/form-data')
@ApiImplicitFormData({ name: 'imageData', required: true, type: 'file' })
@ApiImplicitFormData({ name: 'imageInfo', required: true, type: String })
@ApiImplicitFormData({ name: 'campaignId', required: true, type: String })
doPhotoCampaignUpload(
@Req() req: Request,
@Res() res: Response,
@UploadedFile() file,
@Body() uploadInfo,
) {
try {
Logger.log(file);
Logger.log(uploadInfo);
return res.status(HttpStatus.OK).json({
status: 200,
data: 'On contructing',
});
} catch (error) {
return res.status(HttpStatus.OK).json({
status: -999,
data: 'error',
});
}
}
}
i tested on my project, and it working fine
hope it help.
@see https://github.com/nestjs/swagger/pull/573
To generate correct OpenApi 3 swagger.json you should be able to use:
@Post('upload')
@ApiConsumes('multipart/form-data')
@UseInterceptors(FileInterceptor('file'))
@ApiBody({
type: 'multipart/form-data',
required: true,
schema: {
type: 'object',
properties: {
file: {
type: 'string',
format: 'binary'
}
}
}
})
uploadFile(@UploadedFile() file) {
return file;
}
You can create a custom dto with the file param like
import { ApiProperty } from "@nestjs/swagger";
export class UserDto{
name: string;
@ApiProperty({type:"file"})
avatar?: any;
}
and it makes some sense, because the file is part of the data to be transferred
In case you want to upload _multiple files_ in ^4.6.1 you can use custom dto like
export class ProductDTO {
@ApiProperty({ description: 'Product name' })
name: string;
@ApiProperty({
description: 'Attachments',
type: 'array',
items: {
type: 'file',
items: {
type: 'string',
format: 'binary',
},
},
})
files: any[];
}
then use it on your controller
@Post('/product')
@ApiConsumes('multipart/form-data')
@UseInterceptors(FilesInterceptor('files'))
public async product(
@Body() payload: ProductDTO,
@UploadedFiles() files,
) {
return await this.svc.product(payload, files);
}

since this isn't that well documented I added an S/O Q&A
https://stackoverflow.com/questions/65082784/how-do-i-annotate-an-endpoint-in-nestjs-for-openapi-that-takes-multipart-form-da/65082785#65082785
I made couple of decorator for this purpose:
export const ApiFile = (options?: ApiPropertyOptions): PropertyDecorator => (
target: Object,
propertyKey: string | symbol,
) => {
if (options?.isArray) {
ApiProperty({
type: 'array',
items: {
type: 'file',
properties: {
[propertyKey]: {
type: 'string',
format: 'binary',
},
},
},
})(target, propertyKey);
} else {
ApiProperty({
type: 'file',
properties: {
[propertyKey]: {
type: 'string',
format: 'binary',
},
},
})(target, propertyKey);
}
};
@Injectable()
export class FilesToBodyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const ctx = context.switchToHttp();
const req = ctx.getRequest();
if (req.body && Array.isArray(req.files) && req.files.length) {
req.files.forEach((file: Express.Multer.File) => {
const { fieldname } = file;
if (!req.body[fieldname]) {
req.body[fieldname] = [file];
} else {
req.body[fieldname].push(file);
}
});
}
return next.handle();
}
}
@Injectable()
export class FileToBodyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const ctx = context.switchToHttp();
const req = ctx.getRequest();
if (req.body && req.file?.fieldname) {
const { fieldname } = req.file;
if (!req.body[fieldname]) {
req.body[fieldname] = req.file;
}
}
return next.handle();
}
}
And I use it in the models like this:
export class SingleFileFormDataDTO {
@ApiProperty()
id: string;
@ApiProperty()
description: string;
@ApiFile()
image: Express.Multer.File;
}
export class MultipleFilesFormDataDTO {
@ApiProperty()
id: string;
@ApiProperty()
description: string;
@ApiFile({ isArray: true })
images: Express.Multer.File[];
}
And in the controller:
@Controller('my_route')
export class MyController {
@ApiBody({ type: SingleFileFormDataDTO })
@Post('single')
@ApiConsumes('multipart/form-data')
@UseInterceptors(FileInterceptor('image'), FileToBodyInterceptor)
async uploadSingleFile(
@Body() body: SingleFileFormDataDTO,
): Promise<void> {
if (!body.image) {
throw new BadRequestException('no image file sent.');
}
return;
}
@ApiBody({ type: MultipleFilesFormDataDTO })
@Post('multiple')
@ApiConsumes('multipart/form-data')
@UseInterceptors(FilesInterceptor('images'), FilesToBodyInterceptor)
async uploadMultipleFiles(
@Body() body: MultipleFilesFormDataDTO,
): Promise<void> {
if (!body.images) {
throw new BadRequestException('no image file sent.');
}
return;
}
}
I guess the interceptors and the property decorator should be in nestjs core, does any one from maintainers thinks it worth to make a PR?
maybe, because the issue is Closed, it's not too visible, @kamilmysliwiec as a newcomer to Nest.js, I think it totally worth the effort to at least be documented under the https://docs.nestjs.com/openapi/ section. I would gladly contribute to that, but I have no idea which of the suggested solutions are "preferred", or "idiomatic" to Nest.js..
@kamilmysliwiec I facing the same issue as well. I would like to upload multiple files with different field names with additional form data on a single end point. Is that possible using swagger as of now?
@mushroomgenie if you do the following as above, it will be what you get on swagger:

Hi @karianpour , I'm having some trouble with your proposed solution since the @Body() body: MultipleFilesFormDataDTO, is always empty.
I'm configuring the Nest app with the following code:
dService.use(
json({
limit: jsonLimit,
}),
);
dService.use(urlencoded({ extended: true, limit: urlEncodedLimit }));
My request is something like:
POST <base_url>/api/v1/media/upload
Authorization: Bearer {{authToken}}
Accept: application/json
Content-Type: multipart/form-data; boundary=MyBoundary
--MyBoundary
Content-Disposition: form-data; name="file"; filename="test.http"
Content-Type: application/http
<file_content>
--MyBoundary
Content-Disposition: form-data; name="body"
Content-Type: application/json
{"foo": "bar"}
--MyBoundary--
Do you know why the body is empty?
Thank you.
@massimeddu I checked the request that works with my implementation as it is as follow:
Content-Disposition: form-data; name="id"
1234
------WebKitFormBoundaryBmgnZTxe1QsTRSK3
Content-Disposition: form-data; name="description"
description data here
------WebKitFormBoundaryBmgnZTxe1QsTRSK3
Content-Disposition: form-data; name="images"; filename="0.png"
Content-Type: image/png
<file content>
------WebKitFormBoundaryBmgnZTxe1QsTRSK3
Content-Disposition: form-data; name="images"; filename="20210108_211248#1.jpg"
Content-Type: image/jpeg
<file content>
------WebKitFormBoundaryBmgnZTxe1QsTRSK3--
I guess that you have some problem on the function decorators, here I explain more:
@ApiConsumes('multipart/form-data') // this decorator reads the data (files and fields), and sets files to the request.files property (not body) and sets the fields to body.
@UseInterceptors(FilesInterceptor('images'), FilesToBodyInterceptor) // attention to the 's',
md5-219143406414a8a0cdda5a93ea79ca86
@ApiConsumes('multipart/form-data')
@UseInterceptors(FileInterceptor('file'), FileToBodyInterceptor)
@UseInterceptors(FileInterceptor('body'), FileToBodyInterceptor)
md5-30b47b01c49c1f6a7c0451b819deed65
async uploadFileAndJson(
@Request() req,
): Promise<void> {
console.log(req);
console.log(req.files);
}
Thanks @karianpour , it worked!
I've changed my request in this way:
--MyBoundary
Content-Disposition: form-data; name="file"; filename="test.http"
Content-Type: application/http
<filecontent>
--MyBoundary
Content-Disposition: form-data; name="data"
{"id": 1, "displayLabel": "data"}
--MyBoundary--
Then I've added this new Interceptor:
import { CallHandler, ExecutionContext, NestInterceptor, Type, mixin } from '@nestjs/common';
import { Observable } from 'rxjs';
export function JsonToObjectsInterceptor(fields: string[]): Type<NestInterceptor> {
class JsonToObjectsInterceptorClass implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
if (request.body) {
fields.forEach((field) => {
if (request.body[field]) {
request.body[field] = JSON.parse(request.body[field]);
}
});
}
return next.handle();
}
}
const Interceptor = mixin(JsonToObjectsInterceptorClass);
return Interceptor as Type<NestInterceptor>;
}
And I've applied it:
UseInterceptors(FileInterceptor('file'), FileToBodyInterceptor, JsonToObjectsInterceptor(['data']))
Finally I'm now able to access to body.file and body.data on my Controller.
Thank you for your help!
Most helpful comment
I experience a similar problem. The file upload does work for me using multipart/form-data, and I am successfully sending additional properties with it:
All additional properties are passed to the
modelDatawhen sending a proper POST request from the application.The issue is that swagger does not understand it and the "Try it out" does not work. It sends just the file without any additional data.