# class-validator: decorator-based validation for TypeScript classes

> class-validator turns a TypeScript class into a validation schema, with decorators on properties and a validate() call that returns structured errors. It fits NestJS request pipelines and any codebase already using class-transformer; it is a poor fit if you want a single schema object that also drives type inference.

**typestack/class-validator** — Decorator-based property validation for classes.

- Repository: https://github.com/typestack/class-validator
- Stars: 11,839 · Forks: 848
- Language: TypeScript
- License: MIT
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/typestack-class-validator

## The problem class-validator solves: validation that lives on the class

Most validation libraries ask you to describe your data a second time. You write a TypeScript interface, then you write a separate schema that mirrors it, then you keep the two in sync by hand. class-validator takes the other route. The class is the schema. You annotate properties with decorators such as @IsEmail() or @Length(10, 20), and the class you already use as a type becomes the thing you validate against.

The library is aimed at TypeScript projects that construct objects from untrusted input: HTTP request bodies, message payloads, configuration files. It works in the browser and in Node.js, and internally it delegates the actual string and number checks to validator.js. That delegation matters when you evaluate it. The predicate behaviour for things like @IsFQDN() or @IsEmail() is validator.js behaviour, so if you have opinions about that library's rules, you have the same opinions here.

It is not a schema language and it does not generate types. The decorators are metadata attached to a class at runtime; the TypeScript compiler does not read them. You still declare title: string yourself. What you gain is that the validation rules sit next to the property they constrain, and that a single validate() call returns every failure at once instead of throwing on the first one.

## How validate() works: metadata, traversal and ValidationError objects

Decorators register metadata against the class. When you call validate(post), the library walks the object, reads that metadata, and runs each constraint. The result is an array of ValidationError objects, one per failing property. Each entry carries target (the object that was validated), property (the name that failed), value (the offending value), constraints (a map of constraint name to message), and children for nested objects.

That shape is the part worth understanding before you adopt it. Constraints is a map, not a list, so a property with three failing decorators produces one error with up to three keys. The target field holds a reference to the whole validated object. If you return errors straight to a client, you leak the object. The README addresses this directly: passing validationError: { target: false } removes target from the output, and the README says this is especially useful when you send errors back over HTTP.

There are two entry points. validate() resolves with the error array, so you check errors.length. validateOrReject() rejects the promise with the same array, which suits try/catch in an async handler. Both are asynchronous by default because custom validators may be async; the README also documents a synchronous validation path for cases where you know nothing async is involved.

The options object is where most of the operational decisions live. whitelist strips properties that have no decorators. forbidNonWhitelisted turns unknown properties into errors instead of silently dropping them. groups restricts validation to a named group. stopAtFirstError shortens the error list. And forbidUnknownValues, which the README sets to true by default with an explicit warning to keep it: setting it to false lets unknown objects pass validation. That is the single most important line in the documentation.

## Installing class-validator and running a first validation

The README gives one install command. It notes that you should use at least npm@6, because from that version the dependency tree is flattened, which the README says class-validator requires to function properly. That is a real constraint, not boilerplate: on an older npm the library may not resolve its dependencies as expected.

```bash
npm install class-validator --save
```

After installing, define a class and annotate the properties you care about. This example is adapted from the README's Post class; it uses @Length, @Contains, @IsInt, @Min, @Max, @IsEmail, @IsFQDN and @IsDate.

```typescript
import { validate, Contains, IsInt, Length, IsEmail, IsFQDN, IsDate, Min, Max } from 'class-validator';

export class Post {
  @Length(10, 20)
  title: string;

  @Contains('hello')
  text: string;

  @IsInt()
  @Min(0)
  @Max(10)
  rating: number;

  @IsEmail()
  email: string;

  @IsFQDN()
  site: string;

  @IsDate()
  createDate: Date;
}
```

Instantiate it, assign values, and call validate. The README's own example assigns a title of 'Hello', a rating of 11, and an email of 'google.com', then expects the call to fail.

```typescript
const post = new Post();
post.title = 'Hello';
post.rating = 11;
post.email = 'google.com';

validate(post).then(errors => {
  if (errors.length > 0) {
    console.log('validation failed. errors: ', errors);
  } else {
    console.log('validation succeed');
  }
});
```

What you should see is an array of ValidationError objects. The README shows the expected entry for the short title: property 'title', value 'Hello', and constraints containing a length message reading "$property must be longer than or equal to 10 characters". That $property token is not a typo. Messages support $value, $property, $target and $constraint1 through $constraintN, which are substituted when the error is produced.

To use your own wording, pass a message in the decorator options. The README's example overrides both bounds of a title field.

```typescript
@MinLength(10, { message: 'Title is too short' })
@MaxLength(50, { message: 'Title is too long' })
title: string;
```

If you are wiring this into an HTTP handler, add the target suppression before you serialize anything.

```typescript
validator.validate(post, { validationError: { target: false } });
```

## Where class-validator gets in your way

The decorator approach has a cost that the README does not dwell on. Decorators are runtime metadata, so the rules only exist after the class has been loaded and the decorator module has been imported. If a bundler tree-shakes your decorator imports, or if a build step strips metadata, validation silently does nothing. The package.json sets sideEffects to false, which is correct for bundling but means you should confirm your toolchain keeps the decorator imports.

Plain objects are a second friction point. If you validate an object literal that was never an instance of the class, the library has no metadata to read from it. The README covers validating plain objects and defining a schema without decorators, but those paths exist precisely because the default path expects a class instance. In practice this pushes you toward class-transformer, which converts plain JSON into class instances before validation runs. That is an extra dependency and an extra step, and the README does not present it as optional for the common HTTP-body case.

The error model is verbose. Every failure is an object with five possible fields, and nested structures produce children arrays you have to flatten yourself if you want a simple field-to-message map for a form. There is no built-in serialization format. Compared with libraries that return a flat path-to-message dictionary, this is more work at the boundary.

Finally, the constraint set is validator.js. If your domain needs a rule validator.js does not express, you write a custom validation class or a custom decorator, both of which the README documents. That is a supported extension point, not a workaround, but it means the out-of-the-box coverage is exactly the coverage of the underlying library.

## class-validator vs zod, and when to pick the other one

The comparison people search for is class-validator against zod, and the difference is architectural rather than cosmetic. class-validator attaches rules to a class you write. zod asks you to write a schema value, and the TypeScript type is derived from that schema. With zod, the schema is the source of truth and the type follows; with class-validator, the type is the source of truth and the rules follow.

That single difference decides most adoptions. If you already have classes with decorators, or you are in NestJS where ValidationPipe is built around class-validator and class-transformer, zod means fighting the framework's conventions. If you want one artifact that produces both the runtime check and the static type, and you do not want to keep a class and its annotations in sync, zod is the more direct fit. class-validator cannot infer a type from decorators, and it does not try.

There is a second difference in the error output. zod gives you structured issues with a path array, which maps cleanly onto form fields. class-validator gives you ValidationError objects with a constraints map and a children array for nesting, which you transform yourself. Neither is wrong; they suit different consumers.

A third point of comparison is scope. class-validator is validation only. Transformation, type coercion and property renaming belong to class-transformer, a separate package from the same organisation. If your input needs '25' turned into 25 before @IsInt() runs, that conversion is not this library's job, and the README's plain-object and schema sections are the closest it comes to addressing the gap.

## Maintenance, licence and the upgrade you should plan for

The repository is not archived, and the last push was on 2026-03-25. The most recent release listed is v0.15.1 from 2026-02-26, with v0.15.0 on the same day and v0.14.4 the day before that. The pattern suggests maintenance rather than rapid feature work: a long-lived 0.x line with periodic releases, not a project shipping breaking changes every month.

That 0.x version number is the practical upgrade consideration. A major-version bump can change behaviour without the semantic-versioning signal of a 1.0, so pin the version in package.json and read CHANGELOG.md before moving. The repository keeps one, and it is the only reliable record of what changed between 0.14.4 and 0.15.x. The README does not document a rollback procedure, so treat the changelog as your upgrade plan.

The dependency surface is small: validator, @types/validator and libphonenumber-js. That last one is worth noting because it is pulled in for phone-number validation, and it is a sizeable dependency if you never use that decorator. The package is published with separate cjs, esm5, esm2015 and types entry points, so bundlers pick the right build without configuration.

The licence is MIT, stated in both LICENSE and package.json. That permits commercial use and modification with the copyright notice preserved. This is a description of the licence text, not legal advice; if your organisation has specific obligations around attribution or patent clauses, have counsel read the file.

## Conclusion

Adopt class-validator if your classes already carry decorators, if you use class-transformer to turn plain JSON into instances, or if you are inside NestJS where ValidationPipe expects it. Skip it if you want a schema-first library that infers static types from the schema, or if your runtime is not TypeScript-friendly. Before committing, verify that your build pipeline emits decorator metadata correctly, that forbidUnknownValues stays at its default, and that you pass validationError: { target: false } before sending error payloads over HTTP.

## FAQ

### What is class-validator?

It is a TypeScript library for decorator-based and non-decorator-based validation of class properties. It runs in the browser and in Node.js, and it uses validator.js internally to perform the actual checks.

### Is class-validator available on npm?

Yes. The README's installation section gives the command npm install class-validator --save, and package.json declares the package name as class-validator at version 0.15.1.

### How do I install class-validator?

Run npm install class-validator --save. The README notes you should use at least npm@6, because from that version the dependency tree is flattened, which it says class-validator requires to function properly.

### How do I use class-validator?

Annotate class properties with decorators such as @IsEmail() or @Length(10, 20), create an instance, assign values, then call validate() and inspect the returned ValidationError array, or call validateOrReject() and catch the rejection.

### How do I use class-validator in NestJS?

The README does not document a NestJS integration. It describes the library's own API only, and the repository's sample directory contains framework-independent examples such as simple validation, groups and nested objects.

## Sources

- [Issues](https://github.com/typestack/class-validator/issues)
- [License: MIT](https://github.com/typestack/class-validator/blob/develop/LICENSE)
- [README](https://github.com/typestack/class-validator/blob/develop/README.md)
- [Releases](https://github.com/typestack/class-validator/releases)
- [typestack/class-validator on GitHub](https://github.com/typestack/class-validator)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/typestack-class-validator
