nestjs/nest · warning
Content-Type doesn't match Reply body, you might need a cust
Error message
Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses
What it means
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.
Source
Thrown at packages/platform-express/adapters/express-adapter.ts:131
return response.send();
}
if (body instanceof StreamableFile) {
this.applyStreamHeaders(response, body);
const stream = body.getStream();
stream.once('error', err => {
body.errorHandler(err, response);
});
return stream
.pipe<Writable>(response)
.on('error', (err: Error) => body.errorLogger(err));
}
const responseContentType = response.getHeader('Content-Type');
if (
typeof responseContentType === 'string' &&
!responseContentType.startsWith('application/json') &&
body?.statusCode >= HttpStatus.BAD_REQUEST
) {
this.logger.warn(
"Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses",
);
response.setHeader('Content-Type', 'application/json');
}
return isObject(body) ? response.json(body) : response.send(String(body));
}
public status(response: any, statusCode: number) {
return response.status(statusCode);
}
public end(response: any, message?: string) {
return response.end(message);
}
public render(response: any, view: string, options: any) {
return response.render(view, options);
}View on GitHub (pinned to dd75d7bd8c)
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.
Example fix
// before
@Get('report')
exportReport(@Res() res: Response) {
res.setHeader('Content-Type', 'text/csv');
throw new InternalServerErrorException('boom'); // JSON error body + text/csv header -> warning
}
// after
@Get('report')
async exportReport(@Res() res: Response) {
const csv = await buildReport(); // risky work first
res.setHeader('Content-Type', 'text/csv');
res.send(csv);
} Defensive patterns
Strategy: validation
Validate before calling
// keep the header consistent with the built-in filter's JSON error body
import { catchError, throwError, CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
@Injectable()
export class JsonErrorHeaderInterceptor implements NestInterceptor {
intercept(ctx: ExecutionContext, next: CallHandler) {
return next.handle().pipe(
catchError((err) => {
const res = ctx.switchToHttp().getResponse();
if (!res.headersSent && res.getHeader('Content-Type')) {
res.setHeader('Content-Type', 'application/json');
}
return throwError(() => err);
}),
);
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Content-Type doesn't match Reply body, you might need a cust
- The middleware doesn't provide the 'use' method (${name})
- Conflicting HTTP routes detected: - ${messages} Adjust rou
AI-assisted analysis of nestjs/nest@dd75d7bd8c (2026-08-21).
Data as JSON: /api/errors/a65dacc360421926.
Report an issue: GitHub.