[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"search-suggestions":44,"article-nestjs-weekly-validation-error-target":93,"path-membership-nestjs-weekly-validation-error-target":490,"related-nestjs-weekly-validation-error-target":491,"code:ts:true:1bttu2b":597,"code:ts:true:18irke2":598,"code:ts:true:zf23xe":599,"code:ts:true:xzcs4v":600,"code:ts:true:1go45sk":601,"code:ts:true:1iqkxkk":602,"comments-01a15d15-305d-77da-a29a-d16a7df8ed3f":603},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,[45,49,53,57,61,65,69,73,77,81,85,89],{"slug":46,"name":47,"articles":48},"webdev","Webdev",169,{"slug":50,"name":51,"articles":52},"javascript","Javascript",136,{"slug":54,"name":55,"articles":56},"frontend","Frontend",94,{"slug":58,"name":59,"articles":60},"tutorial","Tutorial",69,{"slug":62,"name":63,"articles":64},"css","Css",50,{"slug":66,"name":67,"articles":68},"performance","Performance",29,{"slug":70,"name":71,"articles":72},"typescript","Typescript",25,{"slug":74,"name":75,"articles":76},"react","React",20,{"slug":78,"name":79,"articles":80},"browser","Browser",18,{"slug":82,"name":83,"articles":84},"programming","Programming",17,{"slug":86,"name":87,"articles":88},"node","Node",15,{"slug":90,"name":91,"articles":92},"html","Html",11,{"id":94,"slug":95,"title":96,"subtitle":97,"excerpt":98,"coverUrl":99,"locale":13,"readingMinutes":100,"publishedAt":101,"viewCount":102,"likeCount":19,"commentCount":19,"author":103,"vertical":108,"topic":109,"tags":112,"_count":117,"playground":119,"body":121,"bodyMd":436,"seo":437,"translationGroupId":439,"series":440,"podcastUrl":97,"verticalId":5,"thread":479,"assessments":481,"translations":486,"quiz":488},"01a15d15-305d-77da-a29a-d16a7df8ed3f","nestjs-weekly-validation-error-target","NestJS 12.2: Why validationError.target Is Now undefined",null,"NestJS 12.2 defaults validationError.target to false, so a custom exceptionFactory reading error.target now gets undefined. The fix, plus a cheat sheet.","\u002Fmedia\u002Fcovers\u002Fnestjs-weekly-validation-error-target.png",12,"2026-11-06T06:10:24.442Z",30,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":110,"name":111},"nestjs","Nestjs",[113,114,115,116],{"slug":110,"name":111,"color":97},{"slug":70,"name":71,"color":97},{"slug":86,"name":87,"color":97},{"slug":58,"name":59,"color":97},{"assessments":118},1,{"slug":95,"title":120},"NestJS validationError.target — ValidationPipe playground",{"blocks":122,"version":118},[123,127,130,135,138,147,150,153,156,170,173,176,182,185,188,191,198,201,204,207,211,214,217,220,224,227,230,233,236,240,243,246,249,253,256,259,267,270,278,281,284,287,290,293,297,300,303,306,310,313,316,352,356,359,367,370,373,376,379,382,385,388,391,394,397,400,403,406,409,412,418,421,424,427,430],{"id":124,"html":125,"type":126},"b1","\u003Cp>A NestJS app upgrades from \u003Ccode>@nestjs\u002Fcore\u003C\u002Fcode> 12.1 to 12.2 — \u003Ccode>npm i @nestjs\u002Fcore@latest @nestjs\u002Fcommon@latest\u003C\u002Fcode>, nothing else touched. The test suite stays green. In production, an audit log that has recorded every failed signup for months — the field that failed, the message, and the full payload the user actually submitted — starts writing \u003Ccode>submittedPayload: undefined\u003C\u002Fcode> on every single entry. No exception, no crash, no warning in the logs. The validation itself still works exactly as before: bad input still gets rejected with a \u003Ccode>400\u003C\u002Fcode>. Only the forensic trail disappeared.\u003C\u002Fp>","paragraph",{"id":128,"html":129,"type":126},"b2","\u003Cp>This is written against \u003Cstrong>\u003Ccode>@nestjs\u002Fcore\u003C\u002Fcode> 12.2.0\u003C\u002Fstrong> (verified 2026-10-20 via \u003Ccode>npm view @nestjs\u002Fcore dist-tags\u003C\u002Fcode> and the \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fnestjs\u002Fnest\u002Freleases\u002Ftag\u002Fv12.2.0\">official v12.2.0 release notes\u003C\u002Fa>; \u003Ccode>12.0.0\u003C\u002Fcode> shipped 2026-08-27, and the \u003Ccode>11.x\u003C\u002Fcode> line remains on the \u003Ccode>legacy\u003C\u002Fcode> npm tag at \u003Ccode>11.2.7\u003C\u002Fcode>). The change described here — \u003Ccode>validationError.target\u003C\u002Fcode> defaulting to \u003Ccode>false\u003C\u002Fcode> — is listed by name in that release&#39;s &quot;new defaults and behaviour changes&quot; section, not inferred from a diff. It applies to the class-validator-based \u003Ccode>ValidationPipe\u003C\u002Fcode> only; it does not touch the newer \u003Ccode>StandardSchemaValidationPipe\u003C\u002Fcode> covered in an \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-12-standard-schema-validation-without-class-validator-161f\">earlier episode of this series\u003C\u002Fa>.\u003C\u002Fp>",{"id":131,"html":132,"text":133,"type":134,"level":31},"b3","What you&#39;ll learn","What you'll learn","heading",{"id":136,"html":137,"type":126},"b4","\u003Cp>By the end of this article you&#39;ll be able to:\u003C\u002Fp>",{"id":139,"type":140,"items":141,"ordered":18},"b5","list",[142,143,144,145,146],"Explain exactly what \u003Ccode>target\u003C\u002Fcode>, \u003Ccode>value\u003C\u002Fcode>, and \u003Ccode>property\u003C\u002Fcode> each mean on a class-validator \u003Ccode>ValidationError\u003C\u002Fcode>, and which of them NestJS&#39;s own built-in \u003Ccode>exceptionFactory\u003C\u002Fcode> actually uses","State precisely why \u003Ccode>@nestjs\u002Fcore\u003C\u002Fcode> 12.2 changed \u003Ccode>validationError.target\u003C\u002Fcode>&#39;s default to \u003Ccode>false\u003C\u002Fcode>, in the maintainers&#39; own words","Fix a custom \u003Ccode>exceptionFactory\u003C\u002Fcode> that silently breaks on the upgrade, without just disabling the new default","Restore the pre-12.2 behavior deliberately, globally or per pipe, when you genuinely need it","Place this change correctly on the request lifecycle&#39;s pipes stage, and rule out the two other places it could be confused for a bug",{"id":148,"html":149,"text":149,"type":134,"level":31},"b6","Who this is for",{"id":151,"html":152,"type":126},"b7","\u003Cp>You&#39;ve registered a \u003Ccode>ValidationPipe\u003C\u002Fcode> — global or scoped — against \u003Ccode>class-validator\u003C\u002Fcode> DTOs at least once, and ideally you&#39;ve written or read a custom \u003Ccode>exceptionFactory\u003C\u002Fcode>. If you haven&#39;t read this series&#39; \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-request-lifecycle-explained-with-cheat-sheet-227l\">NestJS request lifecycle\u003C\u002Fa> episode, it maps exactly where a pipe runs relative to guards, interceptors, and filters — useful background, but this article stands on its own.\u003C\u002Fp>",{"id":154,"html":155,"text":155,"type":134,"level":31},"b8","Table of contents",{"id":157,"type":140,"items":158,"ordered":18},"b9",[159,160,161,162,163,164,165,166,167,168,169],"\u003Ca href=\"#the-problem-an-exceptionfactory-that-used-to-work\">The problem: an exceptionFactory that used to work\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-target-value-and-property-are-three-different-things\">The mental model: target, value, and property are three different things\u003C\u002Fa>","\u003Ca href=\"#stage-1-why-122-changed-the-default\">Stage 1: why 12.2 changed the default\u003C\u002Fa>","\u003Ca href=\"#stage-2-fixing-the-broken-exceptionfactory\">Stage 2: fixing the broken exceptionFactory\u003C\u002Fa>","\u003Ca href=\"#stage-3-restoring-the-old-behavior-on-purpose\">Stage 3: restoring the old behavior on purpose\u003C\u002Fa>","\u003Ca href=\"#stage-4-the-quieter-122-changes-riding-along\">Stage 4: the quieter 12.2 changes riding along\u003C\u002Fa>","\u003Ca href=\"#edge-cases-and-gotchas\">Edge cases and gotchas\u003C\u002Fa>","\u003Ca href=\"#best-practices\">Best practices\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet\">Cheat sheet\u003C\u002Fa>","\u003Ca href=\"#key-takeaways\">Key takeaways\u003C\u002Fa>",{"id":171,"html":172,"text":172,"type":134,"level":31},"b10","The problem: an exceptionFactory that used to work",{"id":174,"html":175,"type":126},"b11","\u003Cp>Here&#39;s the pipe registration behind the audit log in the intro, unchanged across the upgrade:\u003C\u002Fp>",{"id":177,"code":178,"type":179,"language":180,"highlight":181},"b12","\u002F\u002F main.ts\napp.useGlobalPipes(\n  new ValidationPipe({\n    exceptionFactory: (errors) => {\n      const first = errors[0];\n      auditLog.record({\n        field: first.property,\n        message: Object.values(first.constraints ?? {})[0],\n        \u002F\u002F this line is what broke — quietly\n        submittedPayload: first.target,\n      });\n      return new BadRequestException(errors);\n    },\n  }),\n);","code","ts",[],{"id":183,"html":184,"type":126},"b13","\u003Cp>Before 12.2, \u003Ccode>first.target\u003C\u002Fcode> was the whole object \u003Ccode>class-validator\u003C\u002Fcode> had just validated — the full DTO instance built from the request body, every field included, whether it passed or failed. That made it an easy, if slightly lazy, way to log &quot;here&#39;s exactly what the user sent&quot; next to the one field that failed. After 12.2, with nothing else in the application touched, \u003Ccode>first.target\u003C\u002Fcode> is \u003Ccode>undefined\u003C\u002Fcode>. \u003Ccode>submittedPayload: undefined\u003C\u002Fcode> is syntactically valid, so nothing throws — the audit log entry is just worse than it was.\u003C\u002Fp>",{"id":186,"html":187,"text":187,"type":134,"level":31},"b14","The mental model: target, value, and property are three different things",{"id":189,"html":190,"type":126},"b15","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> a class-validator \u003Ccode>ValidationError\u003C\u002Fcode> is a small record for \u003Cstrong>one failing property\u003C\u002Fstrong>, and it can carry up to four separate pieces of information about that failure — and only one of them changed in 12.2:\u003C\u002Fp>",{"id":192,"type":140,"items":193,"ordered":17},"b16",[194,195,196,197],"\u003Cstrong>\u003Ccode>property\u003C\u002Fcode>\u003C\u002Fstrong> — the name of the field that failed, e.g. \u003Ccode>&quot;email&quot;\u003C\u002Fcode>. Always present. Unaffected by this change.","\u003Cstrong>\u003Ccode>constraints\u003C\u002Fcode>\u003C\u002Fstrong> — a map of rule name to human-readable message, e.g. \u003Ccode>{ isEmail: &quot;email must be an email&quot; }\u003C\u002Fcode>. Always present. Unaffected.","\u003Cstrong>\u003Ccode>value\u003C\u002Fcode>\u003C\u002Fstrong> — the actual value that was submitted \u003Cem>for that one field\u003C\u002Fem>, e.g. \u003Ccode>&quot;not-an-email&quot;\u003C\u002Fcode>. This is scoped to the failing property alone. \u003Cstrong>Unaffected by the 12.2 change.\u003C\u002Fstrong>","\u003Cstrong>\u003Ccode>target\u003C\u002Fcode>\u003C\u002Fstrong> — not the field&#39;s value, but \u003Cstrong>the entire object being validated\u003C\u002Fstrong>, with every property, whether it passed or failed. This is the one \u003Ccode>@nestjs\u002Fcore\u003C\u002Fcode> 12.2 now suppresses by default.",{"id":199,"html":200,"type":126},"b17","\u003Cp>That distinction is the whole article: \u003Ccode>value\u003C\u002Fcode> tells you what was wrong with the one field that failed; \u003Ccode>target\u003C\u002Fcode> repeats the complete request body on every single error in the array. A DTO with three failing fields used to produce three \u003Ccode>ValidationError\u003C\u002Fcode> objects, each one carrying a full copy of the same object. NestJS&#39;s own source comment on the change states the reasoning plainly: \u003Cem>&quot;the target of an error is the whole object it was found in, so an exception factory that sends the errors would repeat that object.&quot;\u003C\u002Fem> Repeating it isn&#39;t just noisy — it&#39;s also how a request body with a password field, a token, or any other sensitive value ends up duplicated into every validation error a naive \u003Ccode>exceptionFactory\u003C\u002Fcode> forwards to a client or a log.\u003C\u002Fp>",{"id":202,"html":203,"text":203,"type":134,"level":31},"b18","Stage 1: why 12.2 changed the default",{"id":205,"html":206,"type":126},"b19","\u003Cp>Before \u003Ccode>@nestjs\u002Fcore\u003C\u002Fcode> 12.2, \u003Ccode>ValidationPipe\u003C\u002Fcode> didn&#39;t configure \u003Ccode>validationError\u003C\u002Fcode> for you at all — it passed your \u003Ccode>validatorOptions\u003C\u002Fcode> straight through to class-validator&#39;s \u003Ccode>validate()\u003C\u002Fcode>, and class-validator&#39;s own long-standing default includes \u003Ccode>target\u003C\u002Fcode>. Since 12.2, \u003Ccode>ValidationPipe.transform()\u003C\u002Fcode> builds that option itself, and your own setting — if you have one — wins:\u003C\u002Fp>",{"id":208,"code":209,"type":179,"language":180,"highlight":210},"b20","\u002F\u002F @nestjs\u002Fcommon's ValidationPipe, from 12.2.0 onward (simplified)\nconst errors = await validate(object, {\n  ...this.validatorOptions,\n  validationError: {\n    target: false, \u002F\u002F the new default\n    ...this.validatorOptions.validationError, \u002F\u002F your setting, if any, overrides it\n  },\n});",[],{"id":212,"html":213,"type":126},"b21","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> this is a default \u003Cem>you can always override\u003C\u002Fem>, not a removed feature. If you never touch \u003Ccode>validationError\u003C\u002Fcode>, you now get \u003Ccode>target: false\u003C\u002Fcode>. If you explicitly configure \u003Ccode>validationError: { target: true }\u003C\u002Fcode> on your \u003Ccode>ValidationPipe\u003C\u002Fcode>, that object sits on top of the spread above and wins, on every version from 12.2 onward.\u003C\u002Fp>",{"id":215,"html":216,"text":216,"type":134,"level":31},"b22","Stage 2: fixing the broken exceptionFactory",{"id":218,"html":219,"type":126},"b23","\u003Cp>The honest fix usually isn&#39;t restoring \u003Ccode>target\u003C\u002Fcode> — it&#39;s noticing that \u003Ccode>target\u003C\u002Fcode> was the wrong tool for the job. The audit log wanted &quot;what did the user submit,&quot; and that&#39;s exactly what \u003Ccode>value\u003C\u002Fcode> already gives you, scoped to the field that actually failed, with no risk of dragging unrelated fields (passwords, tokens) along for the ride:\u003C\u002Fp>",{"id":221,"code":222,"type":179,"language":180,"highlight":223},"b24","app.useGlobalPipes(\n  new ValidationPipe({\n    exceptionFactory: (errors) => {\n      const first = errors[0];\n      auditLog.record({\n        field: first.property,\n        message: Object.values(first.constraints ?? {})[0],\n        submittedValue: first.value, \u002F\u002F scoped to this field — works on every 12.x\n      });\n      return new BadRequestException(errors);\n    },\n  }),\n);",[],{"id":225,"html":226,"type":126},"b25","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>value\u003C\u002Fcode> was never touched by the 12.2 change, on any version. If your \u003Ccode>exceptionFactory\u003C\u002Fcode> only needs to know what the \u003Cem>failing field\u003C\u002Fem> contained, switching from \u003Ccode>target\u003C\u002Fcode> to \u003Ccode>value\u003C\u002Fcode> fixes the bug today and keeps working on whatever the default does next.\u003C\u002Fp>",{"id":228,"html":229,"type":126},"b26","\u003Cp>If you genuinely need the whole object — reconstructing the full attempted request for a support ticket, say — read on.\u003C\u002Fp>",{"id":231,"html":232,"text":232,"type":134,"level":31},"b27","Stage 3: restoring the old behavior on purpose",{"id":234,"html":235,"type":126},"b28","\u003Cp>When the full object really is the requirement, say so explicitly instead of relying on an implicit default that already changed once:\u003C\u002Fp>",{"id":237,"code":238,"type":179,"language":180,"highlight":239},"b29","app.useGlobalPipes(\n  new ValidationPipe({\n    validationError: { target: true }, \u002F\u002F explicit — survives future default changes\n    exceptionFactory: (errors) => {\n      const first = errors[0];\n      auditLog.record({\n        field: first.property,\n        message: Object.values(first.constraints ?? {})[0],\n        submittedPayload: first.target, \u002F\u002F present again, on purpose\n      });\n      return new BadRequestException(errors);\n    },\n  }),\n);",[],{"id":241,"html":242,"type":126},"b30","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> this is scoped to wherever you construct that particular \u003Ccode>ValidationPipe\u003C\u002Fcode>. A global pipe with \u003Ccode>validationError: { target: true }\u003C\u002Fcode> restores the old behavior app-wide; a route-scoped \u003Ccode>@UsePipes(new ValidationPipe({ validationError: { target: true } }))\u003C\u002Fcode> restores it only there, leaving the global pipe on the new, leaner default.\u003C\u002Fp>",{"id":244,"html":245,"text":245,"type":134,"level":31},"b31","Stage 4: the quieter 12.2 changes riding along",{"id":247,"html":248,"type":126},"b32","\u003Cp>\u003Ccode>target\u003C\u002Fcode> isn&#39;t the only \u003Ccode>ValidationPipe\u003C\u002Fcode> default the same release changed. Two more are worth knowing before you assume a \u003Ccode>400\u003C\u002Fcode> you&#39;re suddenly seeing is a bug in your code:\u003C\u002Fp>",{"id":250,"code":251,"type":179,"language":180,"highlight":252},"b33","new ValidationPipe({\n  \u002F\u002F New in 12.2 — caps how long the combined message string can get.\n  \u002F\u002F Past this, extra messages are replaced with one \"omitted\" entry.\n  maxErrorMessagesLength: 1_000_000, \u002F\u002F this is already the default\n});",[],{"id":254,"html":255,"type":126},"b34","\u003Cp>The other: sending an \u003Cstrong>array or a bare primitive\u003C\u002Fstrong> where a validated class is expected now gets a \u003Ccode>400\u003C\u002Fcode> instead of silently passing an unvalidated value through. If a route&#39;s DTO is \u003Ccode>CreateUserDto\u003C\u002Fcode> and the body arrives as \u003Ccode>[1, 2, 3]\u003C\u002Fcode> or the string \u003Ccode>&quot;oops&quot;\u003C\u002Fcode>, 12.2 rejects it at the pipe instead of letting it reach the handler shaped nothing like the DTO. Both changes ship in the same release and share the same spirit as the \u003Ccode>target\u003C\u002Fcode> default: stop assuming more than class-validator was actually asked to check.\u003C\u002Fp>",{"id":257,"html":258,"text":258,"type":134,"level":31},"b35","Edge cases and gotchas",{"id":260,"type":140,"items":261,"ordered":18},"b36",[262,263,264,265,266],"\u003Cstrong>The built-in \u003Ccode>exceptionFactory\u003C\u002Fcode> was never affected.\u003C\u002Fstrong> \u003Ccode>@nestjs\u002Fcommon\u003C\u002Fcode>&#39;s own default error formatter — the one \u003Ccode>ValidationPipe\u003C\u002Fcode> uses when you don&#39;t pass your own — only ever reads \u003Ccode>property\u003C\u002Fcode> and \u003Ccode>constraints\u003C\u002Fcode> to build its message array. If you&#39;ve never written a custom \u003Ccode>exceptionFactory\u003C\u002Fcode>, this entire change is invisible to you.","\u003Cstrong>Nested DTOs still carry \u003Ccode>children\u003C\u002Fcode>, not a nested \u003Ccode>target\u003C\u002Fcode>.\u003C\u002Fstrong> A failure on \u003Ccode>address.zip\u003C\u002Fcode> produces a \u003Ccode>ValidationError\u003C\u002Fcode> with \u003Ccode>children: [{ property: &#39;address&#39;, children: [{ property: &#39;zip&#39;, ... }] }]\u003C\u002Fcode>; \u003Ccode>target\u003C\u002Fcode> suppression applies at every level of that tree the same way.","\u003Cstrong>This is specific to class-validator&#39;s \u003Ccode>ValidationPipe\u003C\u002Fcode>.\u003C\u002Fstrong> \u003Ccode>StandardSchemaValidationPipe\u003C\u002Fcode> (covered in \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-12-standard-schema-validation-without-class-validator-161f\">an earlier episode\u003C\u002Fa>) validates with a Standard Schema library&#39;s own \u003Ccode>~standard.validate()\u003C\u002Fcode>, which has no \u003Ccode>target\u003C\u002Fcode> concept at all — nothing here applies to it.","\u003Cstrong>A pipe constructed before you set the option doesn&#39;t retroactively pick it up.\u003C\u002Fstrong> \u003Ccode>validationError\u003C\u002Fcode> is read once, in the constructor&#39;s resolved options — set it when you call \u003Ccode>new ValidationPipe({ ... })\u003C\u002Fcode>, not after.","\u003Cstrong>Logging \u003Ccode>error.target\u003C\u002Fcode> to the console for local debugging is still fine.\u003C\u002Fstrong> The change doesn&#39;t remove the data from the validation run itself — it only removes it from what reaches \u003Ccode>exceptionFactory\u003C\u002Fcode> by default. \u003Ccode>console.log\u003C\u002Fcode> statements inside your own services that build the object from the raw request body are unaffected either way.",{"id":268,"html":269,"text":269,"type":134,"level":31},"b37","Best practices",{"id":271,"type":140,"items":272,"ordered":18},"b38",[273,274,275,276,277],"\u003Cstrong>Default to \u003Ccode>error.value\u003C\u002Fcode>, not \u003Ccode>error.target\u003C\u002Fcode>, in any \u003Ccode>exceptionFactory\u003C\u002Fcode> you write.\u003C\u002Fstrong> It&#39;s scoped to the one field that failed, was never affected by this change, and can&#39;t accidentally leak an unrelated sensitive field.","\u003Cstrong>Never echo the full submitted payload back to the client in an error response\u003C\u002Fstrong>, with or without this default — a validation error is exactly the response an attacker can use to fingerprint which fields your DTO expects.","\u003Cstrong>Set \u003Ccode>validationError: { target: true }\u003C\u002Fcode> explicitly, and only, where you&#39;ve confirmed you need it\u003C\u002Fstrong> — an internal audit log or a support tool, never a response body a client sees.","\u003Cstrong>Audit every custom \u003Ccode>exceptionFactory\u003C\u002Fcode> in the codebase before upgrading to 12.2\u003C\u002Fstrong>, the same way you&#39;d check for any other breaking default: \u003Ccode>grep -rn &quot;exceptionFactory&quot; src\u002F\u003C\u002Fcode> and look for \u003Ccode>.target\u003C\u002Fcode> on the result.","\u003Cstrong>Pin the behavior you rely on, not the default.\u003C\u002Fstrong> The \u003Ccode>target: true\u003C\u002Fcode> default existed for years and still changed once; writing it explicitly means the next default change can&#39;t surprise you again.",{"id":279,"html":280,"text":280,"type":134,"level":31},"b39","FAQ",{"id":282,"html":283,"text":283,"type":134,"level":43},"b40","Does this break validation itself, or just error reporting?",{"id":285,"html":286,"type":126},"b41","\u003Cp>Just error reporting. Validation still runs exactly the same way, and the HTTP response code and the fact that invalid requests get rejected are unchanged. Only what a \u003Cem>custom\u003C\u002Fem> \u003Ccode>exceptionFactory\u003C\u002Fcode> can read off each error changed.\u003C\u002Fp>",{"id":288,"html":289,"text":289,"type":134,"level":43},"b42","Will my app silently stop validating anything after this upgrade?",{"id":291,"html":292,"type":126},"b43","\u003Cp>No. \u003Ccode>target\u003C\u002Fcode> being \u003Ccode>undefined\u003C\u002Fcode> has nothing to do with whether validation ran — it only affects what one specific field on the resulting error object contains.\u003C\u002Fp>",{"id":294,"html":295,"text":296,"type":134,"level":43},"b44","Does \u003Ccode>error.value\u003C\u002Fcode> ever get suppressed the same way?","Does error.value ever get suppressed the same way?",{"id":298,"html":299,"type":126},"b45","\u003Cp>No. The 12.2 release notes and the pipe&#39;s own source only change the \u003Ccode>target\u003C\u002Fcode> default; \u003Ccode>value\u003C\u002Fcode> — the specific field&#39;s submitted value — is untouched on every 12.x version.\u003C\u002Fp>",{"id":301,"html":302,"text":302,"type":134,"level":43},"b46","Is this in the official migration guide, or just the release notes?",{"id":304,"html":305,"type":126},"b47","\u003Cp>It&#39;s listed in the \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fnestjs\u002Fnest\u002Freleases\u002Ftag\u002Fv12.2.0\">v12.2.0 release notes\u003C\u002Fa> under that release&#39;s own &quot;new defaults and behaviour changes,&quot; since it&#39;s a minor-version default change rather than a major-version breaking change requiring a migration guide entry.\u003C\u002Fp>",{"id":307,"html":308,"text":309,"type":134,"level":43},"b48","Can I set \u003Ccode>validationError.target\u003C\u002Fcode> per route instead of globally?","Can I set validationError.target per route instead of globally?",{"id":311,"html":312,"type":126},"b49","\u003Cp>Yes — pass the option to a \u003Ccode>ValidationPipe\u003C\u002Fcode> instance scoped with \u003Ccode>@UsePipes()\u003C\u002Fcode> on a controller or a single handler, the same way any other \u003Ccode>ValidationPipe\u003C\u002Fcode> option can be scoped.\u003C\u002Fp>",{"id":314,"html":315,"text":315,"type":134,"level":31},"b50","Cheat sheet",{"id":317,"head":318,"rows":322,"type":351},"b51",[319,320,321],"Task","Code","Notes",[323,327,331,335,339,343,347],[324,325,326],"Check if your code is affected","\u003Ccode>grep -rn &quot;exceptionFactory&quot; src\u002F\u003C\u002Fcode>","Only custom factories reading \u003Ccode>.target\u003C\u002Fcode> are affected",[328,329,330],"Fix forward (recommended)","Read \u003Ccode>error.value\u003C\u002Fcode> instead of \u003Ccode>error.target\u003C\u002Fcode>","Scoped to the failing field; unaffected by this change",[332,333,334],"Restore old behavior, globally","\u003Ccode>new ValidationPipe({ validationError: { target: true } })\u003C\u002Fcode>","Register once, in \u003Ccode>main.ts\u003C\u002Fcode>",[336,337,338],"Restore old behavior, one route","\u003Ccode>@UsePipes(new ValidationPipe({ validationError: { target: true } }))\u003C\u002Fcode>","Leaves the global pipe on the new default",[340,341,342],"Confirm the built-in factory is unaffected","Don&#39;t pass \u003Ccode>exceptionFactory\u003C\u002Fcode> at all","The default formatter only ever used \u003Ccode>property\u003C\u002Fcode> + \u003Ccode>constraints\u003C\u002Fcode>",[344,345,346],"\u003Ccode>target\u003C\u002Fcode> vs \u003Ccode>value\u003C\u002Fcode>","\u003Ccode>target\u003C\u002Fcode> = whole submitted object · \u003Ccode>value\u003C\u002Fcode> = one field&#39;s value","Only \u003Ccode>target\u003C\u002Fcode> changed its default in 12.2",[348,349,350],"Other 12.2 \u003Ccode>ValidationPipe\u003C\u002Fcode> defaults","\u003Ccode>maxErrorMessagesLength\u003C\u002Fcode> (new), array\u002Fprimitive → \u003Ccode>400\u003C\u002Fcode> (new)","Same release, same &quot;stop assuming more than was checked&quot; theme","table",{"id":353,"code":354,"type":179,"language":180,"highlight":355},"b52","\u002F\u002F main.ts — explicit, upgrade-proof configuration\napp.useGlobalPipes(\n  new ValidationPipe({\n    \u002F\u002F Only set this if something genuinely needs the whole submitted object.\n    \u002F\u002F validationError: { target: true },\n    exceptionFactory: (errors) => {\n      const first = errors[0];\n      return new BadRequestException({\n        field: first.property,\n        message: Object.values(first.constraints ?? {})[0],\n        submittedValue: first.value, \u002F\u002F not first.target\n      });\n    },\n  }),\n);",[],{"id":357,"html":358,"text":358,"type":134,"level":31},"b53","Key takeaways",{"id":360,"type":140,"items":361,"ordered":18},"b54",[362,363,364,365,366],"\u003Ccode>@nestjs\u002Fcore\u003C\u002Fcode> 12.2 defaults \u003Ccode>validationError.target\u003C\u002Fcode> to \u003Ccode>false\u003C\u002Fcode> on \u003Ccode>ValidationPipe\u003C\u002Fcode> — a deliberate change, documented in the v12.2.0 release notes, not a regression.","\u003Ccode>target\u003C\u002Fcode> is the \u003Cstrong>entire validated object\u003C\u002Fstrong>; \u003Ccode>value\u003C\u002Fcode> is the \u003Cstrong>one failing field&#39;s value\u003C\u002Fstrong>. Only \u003Ccode>target\u003C\u002Fcode>&#39;s default changed; \u003Ccode>value\u003C\u002Fcode> never moves.","NestJS&#39;s own built-in \u003Ccode>exceptionFactory\u003C\u002Fcode> never read \u003Ccode>target\u003C\u002Fcode> — this only bites a hand-written one, which is exactly why it fails silently instead of at boot.","Fix forward by reading \u003Ccode>error.value\u003C\u002Fcode>, or restore the old behavior deliberately with \u003Ccode>validationError: { target: true }\u003C\u002Fcode>, globally or per route.","This lives entirely in the pipes stage of the request lifecycle — guards, interceptors, and filters are untouched by it.",{"id":368,"html":369,"text":369,"type":134,"level":31},"b55","Ending",{"id":371,"html":372,"type":126},"b56","\u003Cp>The bug in the intro was never really about NestJS removing something — it was about a default quietly doing less work on an app&#39;s behalf than it used to, in a way that compiles, ships, and only shows up as a slightly worse log entry months later. That&#39;s the same shape of risk every implicit default carries: it&#39;s free until the day it changes, and the fix is almost always the same one — write down what you actually need (\u003Ccode>error.value\u003C\u002Fcode>, not &quot;whatever \u003Ccode>target\u003C\u002Fcode> happens to contain&quot;) instead of leaning on whatever the framework currently hands you.\u003C\u002Fp>",{"id":374,"html":375,"type":126},"b57","\u003Cp>Has a NestJS minor-version upgrade ever changed behavior you didn&#39;t know you were depending on? Drop it in the comments.\u003C\u002Fp>",{"id":377,"html":378,"type":126},"b58","\u003C!-- playground:start -->",{"id":380,"html":381,"text":381,"type":134,"level":31},"b59","🎮 Try it yourself",{"id":383,"html":384,"type":126},"b60","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-validation-error-target\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":386,"html":387,"type":126},"b61","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":389,"html":390,"type":126},"b62","\u003C!-- playground:end -->",{"id":392,"html":393,"type":126},"b63","\u003C!-- quiz:start -->",{"id":395,"html":396,"text":396,"type":134,"level":31},"b64","🧠 Test yourself",{"id":398,"html":399,"type":126},"b65","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-validation-error-target\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":401,"html":402,"type":126},"b66","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":404,"html":405,"type":126},"b67","\u003C!-- quiz:end -->",{"id":407,"html":408,"type":126},"b68","\u003C!-- related:start -->",{"id":410,"html":411,"text":411,"type":134,"level":31},"b69","📚 Read next",{"id":413,"type":140,"items":414,"ordered":18},"b70",[415,416,417],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-standard-schema-validation\">NestJS 12: Standard Schema Validation Without class-validator\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-guards-canactivate-reflector\">NestJS Guards: CanActivate, ExecutionContext &amp; Reflector\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-request-lifecycle\">NestJS Request Lifecycle Explained (with Cheat Sheet)\u003C\u002Fa>",{"id":419,"html":420,"type":126},"b71","\u003C!-- related:end -->",{"id":422,"type":423},"b72","divider",{"id":425,"html":426,"type":126},"b73","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":428,"html":429,"type":126},"b74","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":431,"type":140,"items":432,"ordered":18},"b75",[433,434,435],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","A NestJS app upgrades from `@nestjs\u002Fcore` 12.1 to 12.2 — `npm i @nestjs\u002Fcore@latest @nestjs\u002Fcommon@latest`, nothing else touched. The test suite stays green. In production, an audit log that has recorded every failed signup for months — the field that failed, the message, and the full payload the user actually submitted — starts writing `submittedPayload: undefined` on every single entry. No exception, no crash, no warning in the logs. The validation itself still works exactly as before: bad input still gets rejected with a `400`. Only the forensic trail disappeared.\n\nThis is written against **`@nestjs\u002Fcore` 12.2.0** (verified 2026-10-20 via `npm view @nestjs\u002Fcore dist-tags` and the [official v12.2.0 release notes](https:\u002F\u002Fgithub.com\u002Fnestjs\u002Fnest\u002Freleases\u002Ftag\u002Fv12.2.0); `12.0.0` shipped 2026-08-27, and the `11.x` line remains on the `legacy` npm tag at `11.2.7`). The change described here — `validationError.target` defaulting to `false` — is listed by name in that release's \"new defaults and behaviour changes\" section, not inferred from a diff. It applies to the class-validator-based `ValidationPipe` only; it does not touch the newer `StandardSchemaValidationPipe` covered in an [earlier episode of this series](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-12-standard-schema-validation-without-class-validator-161f).\n\n## What you'll learn\n\nBy the end of this article you'll be able to:\n\n- Explain exactly what `target`, `value`, and `property` each mean on a class-validator `ValidationError`, and which of them NestJS's own built-in `exceptionFactory` actually uses\n- State precisely why `@nestjs\u002Fcore` 12.2 changed `validationError.target`'s default to `false`, in the maintainers' own words\n- Fix a custom `exceptionFactory` that silently breaks on the upgrade, without just disabling the new default\n- Restore the pre-12.2 behavior deliberately, globally or per pipe, when you genuinely need it\n- Place this change correctly on the request lifecycle's pipes stage, and rule out the two other places it could be confused for a bug\n\n## Who this is for\n\nYou've registered a `ValidationPipe` — global or scoped — against `class-validator` DTOs at least once, and ideally you've written or read a custom `exceptionFactory`. If you haven't read this series' [NestJS request lifecycle](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-request-lifecycle-explained-with-cheat-sheet-227l) episode, it maps exactly where a pipe runs relative to guards, interceptors, and filters — useful background, but this article stands on its own.\n\n## Table of contents\n\n- [The problem: an exceptionFactory that used to work](#the-problem-an-exceptionfactory-that-used-to-work)\n- [The mental model: target, value, and property are three different things](#the-mental-model-target-value-and-property-are-three-different-things)\n- [Stage 1: why 12.2 changed the default](#stage-1-why-122-changed-the-default)\n- [Stage 2: fixing the broken exceptionFactory](#stage-2-fixing-the-broken-exceptionfactory)\n- [Stage 3: restoring the old behavior on purpose](#stage-3-restoring-the-old-behavior-on-purpose)\n- [Stage 4: the quieter 12.2 changes riding along](#stage-4-the-quieter-122-changes-riding-along)\n- [Edge cases and gotchas](#edge-cases-and-gotchas)\n- [Best practices](#best-practices)\n- [FAQ](#faq)\n- [Cheat sheet](#cheat-sheet)\n- [Key takeaways](#key-takeaways)\n\n## The problem: an exceptionFactory that used to work\n\nHere's the pipe registration behind the audit log in the intro, unchanged across the upgrade:\n\n```typescript\n\u002F\u002F main.ts\napp.useGlobalPipes(\n  new ValidationPipe({\n    exceptionFactory: (errors) => {\n      const first = errors[0];\n      auditLog.record({\n        field: first.property,\n        message: Object.values(first.constraints ?? {})[0],\n        \u002F\u002F this line is what broke — quietly\n        submittedPayload: first.target,\n      });\n      return new BadRequestException(errors);\n    },\n  }),\n);\n```\n\nBefore 12.2, `first.target` was the whole object `class-validator` had just validated — the full DTO instance built from the request body, every field included, whether it passed or failed. That made it an easy, if slightly lazy, way to log \"here's exactly what the user sent\" next to the one field that failed. After 12.2, with nothing else in the application touched, `first.target` is `undefined`. `submittedPayload: undefined` is syntactically valid, so nothing throws — the audit log entry is just worse than it was.\n\n## The mental model: target, value, and property are three different things\n\n**The mental model:** a class-validator `ValidationError` is a small record for **one failing property**, and it can carry up to four separate pieces of information about that failure — and only one of them changed in 12.2:\n\n1. **`property`** — the name of the field that failed, e.g. `\"email\"`. Always present. Unaffected by this change.\n2. **`constraints`** — a map of rule name to human-readable message, e.g. `{ isEmail: \"email must be an email\" }`. Always present. Unaffected.\n3. **`value`** — the actual value that was submitted *for that one field*, e.g. `\"not-an-email\"`. This is scoped to the failing property alone. **Unaffected by the 12.2 change.**\n4. **`target`** — not the field's value, but **the entire object being validated**, with every property, whether it passed or failed. This is the one `@nestjs\u002Fcore` 12.2 now suppresses by default.\n\nThat distinction is the whole article: `value` tells you what was wrong with the one field that failed; `target` repeats the complete request body on every single error in the array. A DTO with three failing fields used to produce three `ValidationError` objects, each one carrying a full copy of the same object. NestJS's own source comment on the change states the reasoning plainly: *\"the target of an error is the whole object it was found in, so an exception factory that sends the errors would repeat that object.\"* Repeating it isn't just noisy — it's also how a request body with a password field, a token, or any other sensitive value ends up duplicated into every validation error a naive `exceptionFactory` forwards to a client or a log.\n\n## Stage 1: why 12.2 changed the default\n\nBefore `@nestjs\u002Fcore` 12.2, `ValidationPipe` didn't configure `validationError` for you at all — it passed your `validatorOptions` straight through to class-validator's `validate()`, and class-validator's own long-standing default includes `target`. Since 12.2, `ValidationPipe.transform()` builds that option itself, and your own setting — if you have one — wins:\n\n```typescript\n\u002F\u002F @nestjs\u002Fcommon's ValidationPipe, from 12.2.0 onward (simplified)\nconst errors = await validate(object, {\n  ...this.validatorOptions,\n  validationError: {\n    target: false, \u002F\u002F the new default\n    ...this.validatorOptions.validationError, \u002F\u002F your setting, if any, overrides it\n  },\n});\n```\n\n**Key concept:** this is a default *you can always override*, not a removed feature. If you never touch `validationError`, you now get `target: false`. If you explicitly configure `validationError: { target: true }` on your `ValidationPipe`, that object sits on top of the spread above and wins, on every version from 12.2 onward.\n\n## Stage 2: fixing the broken exceptionFactory\n\nThe honest fix usually isn't restoring `target` — it's noticing that `target` was the wrong tool for the job. The audit log wanted \"what did the user submit,\" and that's exactly what `value` already gives you, scoped to the field that actually failed, with no risk of dragging unrelated fields (passwords, tokens) along for the ride:\n\n```typescript\napp.useGlobalPipes(\n  new ValidationPipe({\n    exceptionFactory: (errors) => {\n      const first = errors[0];\n      auditLog.record({\n        field: first.property,\n        message: Object.values(first.constraints ?? {})[0],\n        submittedValue: first.value, \u002F\u002F scoped to this field — works on every 12.x\n      });\n      return new BadRequestException(errors);\n    },\n  }),\n);\n```\n\n**Key concept:** `value` was never touched by the 12.2 change, on any version. If your `exceptionFactory` only needs to know what the *failing field* contained, switching from `target` to `value` fixes the bug today and keeps working on whatever the default does next.\n\nIf you genuinely need the whole object — reconstructing the full attempted request for a support ticket, say — read on.\n\n## Stage 3: restoring the old behavior on purpose\n\nWhen the full object really is the requirement, say so explicitly instead of relying on an implicit default that already changed once:\n\n```typescript\napp.useGlobalPipes(\n  new ValidationPipe({\n    validationError: { target: true }, \u002F\u002F explicit — survives future default changes\n    exceptionFactory: (errors) => {\n      const first = errors[0];\n      auditLog.record({\n        field: first.property,\n        message: Object.values(first.constraints ?? {})[0],\n        submittedPayload: first.target, \u002F\u002F present again, on purpose\n      });\n      return new BadRequestException(errors);\n    },\n  }),\n);\n```\n\n**Key concept:** this is scoped to wherever you construct that particular `ValidationPipe`. A global pipe with `validationError: { target: true }` restores the old behavior app-wide; a route-scoped `@UsePipes(new ValidationPipe({ validationError: { target: true } }))` restores it only there, leaving the global pipe on the new, leaner default.\n\n## Stage 4: the quieter 12.2 changes riding along\n\n`target` isn't the only `ValidationPipe` default the same release changed. Two more are worth knowing before you assume a `400` you're suddenly seeing is a bug in your code:\n\n```typescript\nnew ValidationPipe({\n  \u002F\u002F New in 12.2 — caps how long the combined message string can get.\n  \u002F\u002F Past this, extra messages are replaced with one \"omitted\" entry.\n  maxErrorMessagesLength: 1_000_000, \u002F\u002F this is already the default\n});\n```\n\nThe other: sending an **array or a bare primitive** where a validated class is expected now gets a `400` instead of silently passing an unvalidated value through. If a route's DTO is `CreateUserDto` and the body arrives as `[1, 2, 3]` or the string `\"oops\"`, 12.2 rejects it at the pipe instead of letting it reach the handler shaped nothing like the DTO. Both changes ship in the same release and share the same spirit as the `target` default: stop assuming more than class-validator was actually asked to check.\n\n## Edge cases and gotchas\n\n- **The built-in `exceptionFactory` was never affected.** `@nestjs\u002Fcommon`'s own default error formatter — the one `ValidationPipe` uses when you don't pass your own — only ever reads `property` and `constraints` to build its message array. If you've never written a custom `exceptionFactory`, this entire change is invisible to you.\n- **Nested DTOs still carry `children`, not a nested `target`.** A failure on `address.zip` produces a `ValidationError` with `children: [{ property: 'address', children: [{ property: 'zip', ... }] }]`; `target` suppression applies at every level of that tree the same way.\n- **This is specific to class-validator's `ValidationPipe`.** `StandardSchemaValidationPipe` (covered in [an earlier episode](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-12-standard-schema-validation-without-class-validator-161f)) validates with a Standard Schema library's own `~standard.validate()`, which has no `target` concept at all — nothing here applies to it.\n- **A pipe constructed before you set the option doesn't retroactively pick it up.** `validationError` is read once, in the constructor's resolved options — set it when you call `new ValidationPipe({ ... })`, not after.\n- **Logging `error.target` to the console for local debugging is still fine.** The change doesn't remove the data from the validation run itself — it only removes it from what reaches `exceptionFactory` by default. `console.log` statements inside your own services that build the object from the raw request body are unaffected either way.\n\n## Best practices\n\n- **Default to `error.value`, not `error.target`, in any `exceptionFactory` you write.** It's scoped to the one field that failed, was never affected by this change, and can't accidentally leak an unrelated sensitive field.\n- **Never echo the full submitted payload back to the client in an error response**, with or without this default — a validation error is exactly the response an attacker can use to fingerprint which fields your DTO expects.\n- **Set `validationError: { target: true }` explicitly, and only, where you've confirmed you need it** — an internal audit log or a support tool, never a response body a client sees.\n- **Audit every custom `exceptionFactory` in the codebase before upgrading to 12.2**, the same way you'd check for any other breaking default: `grep -rn \"exceptionFactory\" src\u002F` and look for `.target` on the result.\n- **Pin the behavior you rely on, not the default.** The `target: true` default existed for years and still changed once; writing it explicitly means the next default change can't surprise you again.\n\n## FAQ\n\n### Does this break validation itself, or just error reporting?\n\nJust error reporting. Validation still runs exactly the same way, and the HTTP response code and the fact that invalid requests get rejected are unchanged. Only what a *custom* `exceptionFactory` can read off each error changed.\n\n### Will my app silently stop validating anything after this upgrade?\n\nNo. `target` being `undefined` has nothing to do with whether validation ran — it only affects what one specific field on the resulting error object contains.\n\n### Does `error.value` ever get suppressed the same way?\n\nNo. The 12.2 release notes and the pipe's own source only change the `target` default; `value` — the specific field's submitted value — is untouched on every 12.x version.\n\n### Is this in the official migration guide, or just the release notes?\n\nIt's listed in the [v12.2.0 release notes](https:\u002F\u002Fgithub.com\u002Fnestjs\u002Fnest\u002Freleases\u002Ftag\u002Fv12.2.0) under that release's own \"new defaults and behaviour changes,\" since it's a minor-version default change rather than a major-version breaking change requiring a migration guide entry.\n\n### Can I set `validationError.target` per route instead of globally?\n\nYes — pass the option to a `ValidationPipe` instance scoped with `@UsePipes()` on a controller or a single handler, the same way any other `ValidationPipe` option can be scoped.\n\n## Cheat sheet\n\n| Task | Code | Notes |\n| --- | --- | --- |\n| Check if your code is affected | `grep -rn \"exceptionFactory\" src\u002F` | Only custom factories reading `.target` are affected |\n| Fix forward (recommended) | Read `error.value` instead of `error.target` | Scoped to the failing field; unaffected by this change |\n| Restore old behavior, globally | `new ValidationPipe({ validationError: { target: true } })` | Register once, in `main.ts` |\n| Restore old behavior, one route | `@UsePipes(new ValidationPipe({ validationError: { target: true } }))` | Leaves the global pipe on the new default |\n| Confirm the built-in factory is unaffected | Don't pass `exceptionFactory` at all | The default formatter only ever used `property` + `constraints` |\n| `target` vs `value` | `target` = whole submitted object · `value` = one field's value | Only `target` changed its default in 12.2 |\n| Other 12.2 `ValidationPipe` defaults | `maxErrorMessagesLength` (new), array\u002Fprimitive → `400` (new) | Same release, same \"stop assuming more than was checked\" theme |\n\n```typescript\n\u002F\u002F main.ts — explicit, upgrade-proof configuration\napp.useGlobalPipes(\n  new ValidationPipe({\n    \u002F\u002F Only set this if something genuinely needs the whole submitted object.\n    \u002F\u002F validationError: { target: true },\n    exceptionFactory: (errors) => {\n      const first = errors[0];\n      return new BadRequestException({\n        field: first.property,\n        message: Object.values(first.constraints ?? {})[0],\n        submittedValue: first.value, \u002F\u002F not first.target\n      });\n    },\n  }),\n);\n```\n\n## Key takeaways\n\n- `@nestjs\u002Fcore` 12.2 defaults `validationError.target` to `false` on `ValidationPipe` — a deliberate change, documented in the v12.2.0 release notes, not a regression.\n- `target` is the **entire validated object**; `value` is the **one failing field's value**. Only `target`'s default changed; `value` never moves.\n- NestJS's own built-in `exceptionFactory` never read `target` — this only bites a hand-written one, which is exactly why it fails silently instead of at boot.\n- Fix forward by reading `error.value`, or restore the old behavior deliberately with `validationError: { target: true }`, globally or per route.\n- This lives entirely in the pipes stage of the request lifecycle — guards, interceptors, and filters are untouched by it.\n\n## Ending\n\nThe bug in the intro was never really about NestJS removing something — it was about a default quietly doing less work on an app's behalf than it used to, in a way that compiles, ships, and only shows up as a slightly worse log entry months later. That's the same shape of risk every implicit default carries: it's free until the day it changes, and the fix is almost always the same one — write down what you actually need (`error.value`, not \"whatever `target` happens to contain\") instead of leaning on whatever the framework currently hands you.\n\nHas a NestJS minor-version upgrade ever changed behavior you didn't know you were depending on? Drop it in the comments.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-validation-error-target\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-validation-error-target\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n\u003C!-- related:start -->\n\n## 📚 Read next\n\n- [NestJS 12: Standard Schema Validation Without class-validator](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-standard-schema-validation)\n- [NestJS Guards: CanActivate, ExecutionContext & Reflector](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-guards-canactivate-reflector)\n- [NestJS Request Lifecycle Explained (with Cheat Sheet)](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-request-lifecycle)\n\n\u003C!-- related:end -->\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":96,"canonical":438,"description":98},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-validation-error-target","01a15d15-305d-77da-a29a-d5e3f36200e0",{"name":441,"part":442,"total":442,"items":443},"NestJS Deep Dive",9,[444,449,454,458,462,466,470,474,478],{"slug":445,"title":446,"publishedAt":447,"readingMinutes":448},"nestjs-weekly-request-lifecycle","NestJS Request Lifecycle Explained (with Cheat Sheet)","2026-08-28T17:01:41.941Z",13,{"slug":450,"title":451,"publishedAt":452,"readingMinutes":453},"nestjs-weekly-dependency-injection-providers-scopes","NestJS Dependency Injection Explained (with Cheat Sheet)","2026-09-13T08:40:18.434Z",14,{"slug":455,"title":456,"publishedAt":457,"readingMinutes":88},"nestjs-weekly-module-encapsulation-exports","NestJS Module Encapsulation Explained (with Cheat Sheet)","2026-09-13T08:40:50.070Z",{"slug":459,"title":460,"publishedAt":461,"readingMinutes":448},"nestjs-weekly-testing-provider-overrides","NestJS Testing Module: Provider Overrides (with Cheat Sheet)","2026-09-18T17:23:03.842Z",{"slug":463,"title":464,"publishedAt":465,"readingMinutes":453},"nestjs-weekly-guards-canactivate-reflector","NestJS Guards: CanActivate, ExecutionContext & Reflector","2026-09-26T12:25:39.001Z",{"slug":467,"title":468,"publishedAt":469,"readingMinutes":453},"nestjs-weekly-standard-schema-validation","NestJS 12: Standard Schema Validation Without class-validator","2026-10-02T06:11:02.679Z",{"slug":471,"title":472,"publishedAt":473,"readingMinutes":448},"nestjs-weekly-lifecycle-hook-order","NestJS 12 Lifecycle Hooks: The Order You Can't Assume","2026-10-23T06:10:32.276Z",{"slug":475,"title":476,"publishedAt":477,"readingMinutes":88},"nestjs-weekly-optional-not-inherited","NestJS 12: @Optional() Not Inherited by a Redeclared Constructor","2026-10-30T05:36:46.066Z",{"slug":95,"title":96,"publishedAt":101,"readingMinutes":100},{"id":480,"locked":18},"01a15d15-308b-7459-9265-9fcc0f028b8e",[482],{"id":483,"slug":95,"title":96,"_count":484},"01a15d15-30bb-7797-9b5c-c7afc119dcae",{"questions":485},8,[487],{"locale":13,"slug":95},{"id":483,"slug":95,"title":96,"_count":489,"questionCount":485},{"questions":485},[],{"items":492,"meta":595},[493,505,520,535,550,565,580],{"id":94,"slug":95,"title":96,"subtitle":97,"excerpt":98,"coverUrl":99,"locale":13,"readingMinutes":100,"publishedAt":101,"viewCount":494,"likeCount":19,"commentCount":19,"author":495,"vertical":496,"topic":497,"tags":498,"_count":503,"playground":504,"hasQuiz":17,"hasPlayground":17},31,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":110,"name":111},[499,500,501,502],{"slug":110,"name":111,"color":97},{"slug":70,"name":71,"color":97},{"slug":86,"name":87,"color":97},{"slug":58,"name":59,"color":97},{"assessments":118},{"slug":95},{"id":506,"slug":475,"title":476,"subtitle":97,"excerpt":507,"coverUrl":508,"locale":13,"readingMinutes":88,"publishedAt":477,"viewCount":509,"likeCount":19,"commentCount":19,"author":510,"vertical":511,"topic":512,"tags":513,"_count":518,"playground":519,"hasQuiz":17,"hasPlayground":17},"01a11150-cc6d-7657-8fe5-ae7578a8058b","NestJS 12 stops inheriting @Optional() once a subclass redeclares its own constructor — not when it has none at all. Cheat sheet included.","\u002Fmedia\u002Fcovers\u002Fnestjs-weekly-optional-not-inherited.png",226,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":110,"name":111},[514,515,516,517],{"slug":110,"name":111,"color":97},{"slug":70,"name":71,"color":97},{"slug":86,"name":87,"color":97},{"slug":58,"name":59,"color":97},{"assessments":118},{"slug":475},{"id":521,"slug":471,"title":472,"subtitle":97,"excerpt":522,"coverUrl":523,"locale":13,"readingMinutes":448,"publishedAt":473,"viewCount":524,"likeCount":19,"commentCount":19,"author":525,"vertical":526,"topic":527,"tags":528,"_count":533,"playground":534,"hasQuiz":17,"hasPlayground":17},"01a1358a-158e-70df-8178-64af7e9a5932","NestJS 12 changed how lifecycle hooks are ordered. Learn what onModuleInit and onApplicationBootstrap really guarantee, plus a copy-paste cheat sheet.","\u002Fmedia\u002Fcovers\u002Fnestjs-weekly-lifecycle-hook-order.png",294,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":110,"name":111},[529,530,531,532],{"slug":110,"name":111,"color":97},{"slug":70,"name":71,"color":97},{"slug":86,"name":87,"color":97},{"slug":58,"name":59,"color":97},{"assessments":118},{"slug":471},{"id":536,"slug":467,"title":468,"subtitle":97,"excerpt":537,"coverUrl":538,"locale":13,"readingMinutes":453,"publishedAt":469,"viewCount":539,"likeCount":19,"commentCount":19,"author":540,"vertical":541,"topic":542,"tags":543,"_count":548,"playground":549,"hasQuiz":17,"hasPlayground":17},"01a0f0ef-ccc8-73e8-9b53-e6ce4c7fe9ff","NestJS 12 adds Standard Schema validation for @Body(), @Query(), and @Param() — validate with Zod or Valibot, no class-validator DTO. Cheat sheet included.","\u002Fmedia\u002Fcovers\u002Fnestjs-weekly-standard-schema-validation.png",605,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":110,"name":111},[544,545,546,547],{"slug":110,"name":111,"color":97},{"slug":86,"name":87,"color":97},{"slug":70,"name":71,"color":97},{"slug":58,"name":59,"color":97},{"assessments":118},{"slug":467},{"id":551,"slug":463,"title":464,"subtitle":97,"excerpt":552,"coverUrl":553,"locale":13,"readingMinutes":453,"publishedAt":465,"viewCount":554,"likeCount":19,"commentCount":19,"author":555,"vertical":556,"topic":557,"tags":558,"_count":563,"playground":564,"hasQuiz":17,"hasPlayground":17},"01a0ddae-48ac-73c6-bdee-1420cdfb17c2","A complete guide to NestJS guards: how CanActivate, ExecutionContext, and the Reflector decide who reaches your route handler, with a cheat sheet.","\u002Fmedia\u002Fcovers\u002Fnestjs-weekly-guards-canactivate-reflector.png",739,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":110,"name":111},[559,560,561,562],{"slug":110,"name":111,"color":97},{"slug":86,"name":87,"color":97},{"slug":70,"name":71,"color":97},{"slug":58,"name":59,"color":97},{"assessments":118},{"slug":463},{"id":566,"slug":459,"title":460,"subtitle":97,"excerpt":567,"coverUrl":568,"locale":13,"readingMinutes":448,"publishedAt":461,"viewCount":569,"likeCount":19,"commentCount":19,"author":570,"vertical":571,"topic":572,"tags":573,"_count":578,"playground":579,"hasQuiz":17,"hasPlayground":17},"01a0a630-c856-72d3-bae5-3339e38a8564","A complete guide to NestJS's testing module: how overrideProvider really wires mocks, why get() fails on scoped providers, and using useMocker.","\u002Fmedia\u002Fcovers\u002Fnestjs-weekly-testing-provider-overrides.png",714,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":110,"name":111},[574,575,576,577],{"slug":110,"name":111,"color":97},{"slug":86,"name":87,"color":97},{"slug":70,"name":71,"color":97},{"slug":58,"name":59,"color":97},{"assessments":118},{"slug":459},{"id":581,"slug":455,"title":456,"subtitle":97,"excerpt":582,"coverUrl":583,"locale":13,"readingMinutes":88,"publishedAt":457,"viewCount":584,"likeCount":19,"commentCount":19,"author":585,"vertical":586,"topic":587,"tags":588,"_count":593,"playground":594,"hasQuiz":17,"hasPlayground":17},"01a0821b-4733-762d-bf96-b68c7eb140c0","How NestJS module boundaries actually work: what exports really cross, why @Global() isn't a shortcut, and how forwardRef breaks circular module dependencies.","\u002Fmedia\u002Fcovers\u002Fnestjs-weekly-module-encapsulation-exports.png",639,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":110,"name":111},[589,590,591,592],{"slug":110,"name":111,"color":97},{"slug":86,"name":87,"color":97},{"slug":70,"name":71,"color":97},{"slug":58,"name":59,"color":97},{"assessments":118},{"slug":455},{"page":118,"perPage":596,"total":442,"totalPages":31},7,"\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F main.ts\n\u003C\u002Fspan>app\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">useGlobalPipes\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">ValidationPipe\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    exceptionFactory\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>errors\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> first \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> errors\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>;\n      auditLog\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">record\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n        field\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> first\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>property\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n        message\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Object\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">values\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>first\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>constraints \u003Cspan class=\"shj-oper\">??\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n        \u003Cspan class=\"shj-cmnt\">\u002F\u002F this line is what broke — quietly\n\u003C\u002Fspan>        submittedPayload\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> first\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>target\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n      \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n      \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">BadRequestException\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>errors\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F @nestjs\u002Fcommon's ValidationPipe, from 12.2.0 onward (simplified)\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> errors \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">validate\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>object\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-oper\">...\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">this\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>validatorOptions\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  validationError\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    target\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">false\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F the new default\n\u003C\u002Fspan>    \u003Cspan class=\"shj-oper\">...\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">this\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>validatorOptions\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>validationError\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F your setting, if any, overrides it\n\u003C\u002Fspan>  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">app\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">useGlobalPipes\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">ValidationPipe\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    exceptionFactory\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>errors\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> first \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> errors\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>;\n      auditLog\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">record\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n        field\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> first\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>property\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n        message\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Object\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">values\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>first\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>constraints \u003Cspan class=\"shj-oper\">??\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n        submittedValue\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> first\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F scoped to this field — works on every 12.x\n\u003C\u002Fspan>      \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n      \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">BadRequestException\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>errors\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">app\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">useGlobalPipes\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">ValidationPipe\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    validationError\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> target\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F explicit — survives future default changes\n\u003C\u002Fspan>    exceptionFactory\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>errors\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> first \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> errors\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>;\n      auditLog\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">record\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n        field\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> first\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>property\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n        message\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Object\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">values\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>first\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>constraints \u003Cspan class=\"shj-oper\">??\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n        submittedPayload\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> first\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>target\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F present again, on purpose\n\u003C\u002Fspan>      \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n      \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">BadRequestException\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>errors\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">ValidationPipe\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-cmnt\">\u002F\u002F New in 12.2 — caps how long the combined message string can get.\n\u003C\u002Fspan>  \u003Cspan class=\"shj-cmnt\">\u002F\u002F Past this, extra messages are replaced with one \"omitted\" entry.\n\u003C\u002Fspan>  maxErrorMessagesLength\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">1_000_000\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F this is already the default\n\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F main.ts — explicit, upgrade-proof configuration\n\u003C\u002Fspan>app\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">useGlobalPipes\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">ValidationPipe\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-cmnt\">\u002F\u002F Only set this if something genuinely needs the whole submitted object.\n\u003C\u002Fspan>    \u003Cspan class=\"shj-cmnt\">\u002F\u002F validationError: { target: true },\n\u003C\u002Fspan>    exceptionFactory\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>errors\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> first \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> errors\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>;\n      \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">BadRequestException\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n        field\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> first\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>property\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n        message\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Object\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">values\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>first\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>constraints \u003Cspan class=\"shj-oper\">??\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n        submittedValue\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> first\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F not first.target\n\u003C\u002Fspan>      \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":604},[]]