Build1 publisher2 min readPublished
NestJS 12 skips @Body schema validation unless StandardSchemaValidationPipe is registered
NestJS 12's schema option on @Body, @Query and @Param validates nothing until StandardSchemaValidationPipe is registered. A team that swaps class-validator DTOs for Zod schemas during the upgrade can ship unchecked routes and never see an error.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

What happened
- Against @nestjs/core 12.1.1 with no pipes registered, a POST /users with an empty body reached the handler as {} with no 400, no exception and nothing logged.
- Pipes unaware of the new schema field ignore it, and that includes the class-validator ValidationPipe many v11 apps register globally.
- StandardSchemaValidationPipe, the schema option and a response-side StandardSchemaSerializerInterceptor all arrived in the v12 line and do not exist in v11.
- NestJS 12.0.0 shipped on 2026-08-27, and npm now carries the 11.x line under the legacy tag.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- exposure A missing field surfaces at the first line of business logic that trusts the z.infer type, far from the request boundary where a 400 belonged.
- constraint A v11 bootstrap carried over unchanged leaves schema routes uncovered even with its global ValidationPipe still in place.
- cost Happy-path tests pass either way, so each schema route needs a negative test with a missing field to prove the pipe is wired.
- decision Global registration covers every schema route at once; per-route registration leaves each new schema route unvalidated until someone attaches the pipe.
The handler in the walkthrough types its parameter as `z.infer<typeof createUserSchema>`. That schema requires `name` to be a non-empty string [11]. So the compiler tells every line of the handler that `body.name` is a string, while at runtime it was undefined [4]. "The schema was real. The validation never ran," the walkthrough's author wrote [14].
Every decorator that accepts `{ schema }` does one thing with it. The list is `@Body()`, `@Query()`, `@Param()` and `@RawBody()`, and the one thing is attaching the object to the parameter's `ArgumentMetadata`, next to the existing `type`, `data` and `metatype` fields [6]. At that point the Zod object is a very thorough comment. The router then runs every pipe configured for the parameter, global first, then controller, method and param, and hands each one the same metadata, schema included [7]. In v12 the pipe written to act on that field is `StandardSchemaValidationPipe`. It has to be registered, just as `ValidationPipe` had to be in v11 [9].
Coercion is lost too. The example schema converts `age` with `z.coerce.number()` [11]. With no pipe reading the schema, that conversion never runs and the handler gets whatever the client sent [2]. Once the pipe is in place, its `transform` option decides whether the handler sees the schema's transformed value or the original input [12]. The error shape has its own hook, `exceptionFactory` [13].
I think the split is sound engineering. Decorators hold metadata and pipes are the only code that executes. According to the walkthrough, class-validator DTOs and Standard Schema routes can coexist in one app, with the new pipe registered globally or per route next to an existing `ValidationPipe` [10]. A migration can therefore move one route at a time. The cost is the silent default. In the walkthrough's run, the unread schema left nothing in the logs [5]. I'd want a boot-time warning for any parameter that carries a schema with no schema-aware pipe in its chain.
The walkthrough says it took this behaviour from the published `@nestjs/common` and `@nestjs/core` source for 12.1.1, which it checked against npm on 2026-09-29 [4][2].
What to watch
- Whether a later 12.x release warns or fails at boot when a parameter carries a schema and no schema-aware pipe sits in its chain.
- Whether StandardSchemaSerializerInterceptor, the response-side counterpart, has the same register-or-nothing default.
- Whether official NestJS v12 migration guidance tells teams replacing class-validator DTOs to register StandardSchemaValidationPipe.