{"record":{"id":"a65dacc360421926","repo":"nestjs/nest","slug":"content-type-doesn-t-match-reply-body-you-might-n","errorCode":null,"errorMessage":"Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses","messagePattern":"Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/platform-express/adapters/express-adapter.ts","lineNumber":131,"sourceCode":"      return response.send();\n    }\n    if (body instanceof StreamableFile) {\n      this.applyStreamHeaders(response, body);\n      const stream = body.getStream();\n      stream.once('error', err => {\n        body.errorHandler(err, response);\n      });\n      return stream\n        .pipe<Writable>(response)\n        .on('error', (err: Error) => body.errorLogger(err));\n    }\n    const responseContentType = response.getHeader('Content-Type');\n    if (\n      typeof responseContentType === 'string' &&\n      !responseContentType.startsWith('application/json') &&\n      body?.statusCode >= HttpStatus.BAD_REQUEST\n    ) {\n      this.logger.warn(\n        \"Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses\",\n      );\n      response.setHeader('Content-Type', 'application/json');\n    }\n    return isObject(body) ? response.json(body) : response.send(String(body));\n  }\n\n  public status(response: any, statusCode: number) {\n    return response.status(statusCode);\n  }\n\n  public end(response: any, message?: string) {\n    return response.end(message);\n  }\n\n  public render(response: any, view: string, options: any) {\n    return response.render(view, options);\n  }","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/nestjs/nest/blob/dd75d7bd8c5e88048587e6768d36eb695f3e7a25/packages/platform-express/adapters/express-adapter.ts#L113-L149","documentation":"ExpressAdapter.reply() runs for bodies Nest sends through the adapter, including the built-in exception filter's {statusCode, message, error} payload. If the response already has a string Content-Type header that is not application/json while the body is an object with statusCode >= 400, the adapter warns and force-sets Content-Type to application/json before JSON-serializing with response.json(). The warning flags the inconsistency: something set a non-JSON content type on a response that ends up carrying a JSON error body.","triggerScenarios":"A handler or interceptor calls res.setHeader('Content-Type', 'text/html' / 'text/csv' / ...) and the request then fails with an HttpException (>= 400), so the built-in exception filter writes its JSON error body through reply() while the stale non-JSON header is still set. Also custom exception filters that set a non-JSON Content-Type but delegate the actual body to the framework instead of writing it themselves.","commonSituations":"CSV/file-download endpoints that set headers before generating the file, then throw; SSR or hybrid apps mixing HTML templates with JSON API errors; middleware or template engines setting Content-Type early; the warning appearing after upgrading Nest to a release that added this consistency check.","solutions":["Defer setting Content-Type until after the failure-prone work: generate the CSV/file first, then set headers and send.","Make custom ExceptionFilters fully own the response: set status + matching Content-Type and write the body (res.status(...).send/render/json) rather than setting headers and letting the built-in filter serialize.","Scope interceptors that set Content-Type away from routes whose error responses are JSON, or reset the header on the error path.","Add e2e assertions that error responses keep an application/json Content-Type to catch regressions."],"exampleFix":"// before\n@Get('report')\nexportReport(@Res() res: Response) {\n  res.setHeader('Content-Type', 'text/csv');\n  throw new InternalServerErrorException('boom'); // JSON error body + text/csv header -> warning\n}\n\n// after\n@Get('report')\nasync exportReport(@Res() res: Response) {\n  const csv = await buildReport(); // risky work first\n  res.setHeader('Content-Type', 'text/csv');\n  res.send(csv);\n}","handlingStrategy":"validation","validationCode":"// keep the header consistent with the built-in filter's JSON error body\nimport { catchError, throwError, CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';\n\n@Injectable()\nexport class JsonErrorHeaderInterceptor implements NestInterceptor {\n  intercept(ctx: ExecutionContext, next: CallHandler) {\n    return next.handle().pipe(\n      catchError((err) => {\n        const res = ctx.switchToHttp().getResponse();\n        if (!res.headersSent && res.getHeader('Content-Type')) {\n          res.setHeader('Content-Type', 'application/json');\n        }\n        return throwError(() => err);\n      }),\n    );\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Set Content-Type only immediately before writing the success body, never before work that can throw","Custom ExceptionFilters must write the full response (status + content type + body), never half-set headers and delegate","e2e-test error paths and assert response Content-Type matches the body format","Avoid setting Content-Type in interceptors for routes whose errors are serialized as JSON"],"tags":["express","http","content-type","exception-filter","http-errors"],"backgroundTag":"content-type-mismatch-error-response","analyzedSha":"dd75d7bd8c5e88048587e6768d36eb695f3e7a25","analyzedAt":"2026-08-21T19:39:39.867Z","contentChangedAt":"2026-08-21T19:39:39.867Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}