TypeBox is a fast and type-safe way to validate data in TypeScript. It lets you define your data structure once and uses that same definition for both type checking and runtime validation. This means you don’t have to write separate types and validation rules—TypeBox handles both.
It’s built specifically for TypeScript and works great when you want strict, reliable data validation without slowing things down. The API is clean, easy to use, and solid performance—even for large data sets.
In this guide, you’ll use TypeBox in a TypeScript project. You’ll define schemas, validate data, handle errors, and use more advanced patterns when needed.
Prerequisites
Before you begin, make sure you have:
- Node.js (version 22 or newer) installed
- A basic understanding of TypeScript
Setting up the project
In this section, you’ll set up a basic TypeScript environment so you can start using TypeBox right away.
First, create a new project directory and initialize it as an npm project:
Install TypeScript as a dev dependency and TypeBox as a regular dependency:
Create a basic TypeScript configuration:
Now, update your package.json to make it easy to run TypeScript files:
Install tsx for running TypeScript files directly:
You're now ready to write and run TypeScript code using TypeBox.
Getting started with TypeBox
Now that your project is set up, let’s create your first TypeBox schema.
Create an index.ts file in your project root with the following content:
In this code, you define a simple UserSchema using TypeBox, which describes what a valid user object should look like—containing a string name, a number age, and a string email. Then, you create a sample userData object that matches the schema.
Using TypeCompiler.Compile, you turn the schema into a fast, reusable validation function. The Check method runs the actual validation, returning true if the data matches the schema or false if it doesn’t.
To try it out, run the script with:
You've just created your first TypeBox schema! This example defines a simple UserSchema with three properties and checks if a userData object matches it. TypeBox uses a compiler-based approach to generate fast validation functions, which makes it great for performance.
Basic schema validation
Now, let's enhance our validation by adding more constraints to our schema:
In the highlighted code, you add constraints to the schema:
namemust be between 2 and 50 charactersagemust be between 18 and 120emailmust follow a valid email format
You then create an invalidUser object that purposely breaks all three rules. The code checks if this object is valid using validator.Check(), and if it’s not, it prints detailed validation errors using validator.Errors().
Run the code again:
You’ll see:
Note that the Errors method returns an iterator that needs to be converted to an array (using the spread operator [...]) to display the full error details. Each error object includes the failing validation rule's location, the invalid property's path, and a human-readable error message.
Type inference with TypeBox
One of TypeBox’s biggest strengths is its ability to generate TypeScript types directly from your schemas. This keeps your validation and type definitions in sync without any extra work. You define your schema once, and TypeBox gives you both runtime validation and compile-time type safety.
Let’s see how it works in practice.
Create a new file called type-inference.ts:
In this code, you define a UserSchema using TypeBox, then use typeof UserSchema.static to automatically generate a matching TypeScript type called User.
This means you don’t need to write a separate type definition manually—TypeBox handles it for you. You then create a user object that matches the schema, and TypeScript will catch any mistakes if the structure doesn’t match. This keeps your validation and types ideally in sync.
Run this new file:
The key part is type User = typeof UserSchema.static, which extracts a TypeScript type from your schema. TypeScript now knows precisely what properties and types a User should have.
Try experimenting with invalid types. Add this to the end of your file:
Your editor will immediately show errors for the incorrect types. TypeScript knows that age must be a number and isActive must be a boolean.
Optional and nullable properties
Real-world data often includes properties that might be missing or have null values. TypeBox makes it easy to define and validate these types of fields in your schemas, extending what you've already learned about basic property validation.
Create a new file called optional-nullable.ts:
In this example, you define a schema with several types of fields:
username: A required string field (just like in previous examples)-
displayName: An optional string field (may be omitted) bio: A nullable string field (must be present but can be null)website: A field that can be either a string or nullage: An optional number field (may be omitted)
The key difference in this example is how we handle nullable fields. Rather than using a dedicated Nullable function (which may not be available in all TypeBox versions), we use Type.Union([Type.String(), Type.Null()]) to create a field that accepts either a string or null value.
Run the example:
As you can see, TypeBox validates that required fields must be present, while allowing optional fields to be omitted.
Nested objects and arrays
Most real applications need to validate complex data with nested objects and arrays. TypeBox makes this easy by letting you compose schemas.
Create a new file called nested-schemas.ts:
In this example, you define a separate AddressSchema for an address and nest it inside the main UserSchema to represent more complex, structured data. You also include a tags field, which is an array of strings.
This shows how TypeBox makes building schemas that mirror real-world data shapes with nested objects and arrays easy.
The user object is then validated against the full schema using validator.Check(), confirming that all nested fields and array values meet the expected structure.
Run the example:
Try creating an invalid user by removing the city or putting a number in the tags array. TypeBox will catch these errors, even in nested properties, and report exactly where the problem is.
Composing schemas this way lets you build validation for increasingly complex data while maintaining both type safety and runtime validation.
Custom formats and validators
Sometimes the built-in validators aren't enough for your specific requirements. TypeBox allows you to create custom formats and validation rules to handle these cases.
Create a new file called custom-validation.ts:
In this example, you create a custom format validator for product codes that must follow a specific pattern: three uppercase letters, a hyphen, and five digits.
The key part is using FormatRegistry.Set() to register a custom format validator. The validator is a function that returns true when the value is valid or false when it's not.
Run the code:
Custom formats are perfect for domain-specific validation requirements like:
- Special identifiers (product codes, SKUs, etc.)
- Custom date formats
- Username or password requirements
- Business-specific rules
When TypeBox's built-in validators don't cover your specific needs, custom formats give you the flexibility to implement exactly the validation rules your application requires.
Formatting validation errors
TypeBox gives detailed error information, but it's often too technical for end users. Let's see how to transform these errors into user-friendly messages.
Create a new file called error-formatting.ts:
In this example, you define a RegistrationSchema with several validation rules—like minimum lengths for username and password, a valid email format, and a minimum age requirement. You then create an invalidData object that intentionally breaks these rules.
Using validator.Check(), you validate the data, and when it fails, you collect and print the raw validation errors using validator.Errors(). The output includes detailed info about what failed, where, and why—making it easy to format or display user-friendly error messages later.
Run the example to see the raw errors:
As you can see, this output isn’t very user-friendly on its own.
Now, let's add a simple function to format these errors into cleaner, human-readable messages that you could display in a UI or return from an API response.
This helps users quickly understand what went wrong and how to fix it. Add the highlighted code below:
In this code, you add a formatErrors function that takes the raw validation errors from TypeBox and turns them into simple, user-friendly messages.
It loops through each error, extracts the field name from the path, and assigns a message based on the type of validation that failed (like minLength, format, or minimum). This lets you show clear feedback like "This field is too short" instead of a technical message.
Finally, the formatted errors are logged as an object, making them easy to display in a UI or return in an API response.
Run the updated example to see the cleaned-up messages:
Converting TypeBox's detailed validation errors into simple messages makes your application more user-friendly while benefiting from TypeBox's powerful validation capabilities.
Final thoughts
Throughout this guide, you've learned how to use TypeBox to create robust validation in TypeScript applications. You've explored basic schemas, constraints, type inference, optional fields, nested objects, custom formats, and error handling.
TypeBox effectively bridges compile-time type checking and runtime validation with a single schema definition. Its compiler-based approach delivers excellent performance while maintaining strong type safety.
For more advanced features and detailed API documentation, visit the official TypeBox repository. As your applications grow in complexity, TypeBox's extensive capabilities can help ensure data integrity at every level while keeping your codebase clean and maintainable.