Type inferring from validation schema
Posted on August 11, 2025
Validating the data is a must when handling forms. It’s common to use trusted libraries like Zod.
Before creating our lightweight validation system, let’s explore Zod’s API. At first, we should create a validation schema:
The second step is to validate the specific value based on the created schema:
The exciting part here is when you have a look at the inferred Typescript type of value according to the created schema:

Picture 1 — Zod syntax for validation value according to defined schema with inferred TS type
Moreover, Zod has its utility type that allows us to infer the type from schema:

Picture 2 — Explicit inffering type from Zod Schema
The main goal of this article is to implement this utility type. We’re not going to re-implement the entire library. Instead, the primary goal is to implement a few simple things that will help deeply understand the core concepts and underlying ideas.
Defining interface for validators
We are going to implement a few basic validators. Let’s have a look at Picture 1 and note that each validator includes at least one method — parse
. With this in mind, let's create an interface for the validators:
Here, we are using a generic type, which is like a function with parameters. But instead of passing values as parameters, we pass types.
In the parse
method, we've introduced the unknown
type for the value. This type represents a value that could be of any type, but unlike any
, it requires developers to perform type-checking before interacting with the value. This suits our use case, where we may need to validate values of various types, forcing us to do a bunch of type-checking.
Implementing the String validator
It seems that we are ready to create the first validator for string values:
It's nothing special, but I would like to emphasize a few key points here. The parse
method initially has a value with unknown
type. Therefore, our first step is to check if this value is a string. As soon as we do this check — typeof value !== string
and throw error — Typescript understands that after this if-block — value
will have exactly the type string
:
This is a feature in TypeScript called Type Guards. If you’re unfamiliar with it, I highly recommend exploring and understanding this powerful mechanism.
Now, returning to our main topic, we have everything we need in place. Let’s go ahead and create our first validation schema and attempt to retrieve the value after validation:
How do you think, what the type will be for validatedValue
?
Here is an answer:

It is probably not impressive — since Typescript infers and unwraps type from the generic interface ( Schema<T>
). But let’s try to create our utility type like Zod has, which would extract the type from the schema (see picture 2).
Inferring type from schema
We should know about one advanced TS feature — Conditional Type Inference. It allows us to infer or extract types based on conditions. It’s used with conditional types, where we check if one type extends another type (using extends
), and if the condition is true, we can "infer" a part of the type using the infer
keyword. Let’s have a look at the implementation for one built-in utility type in TS:
It means the following:
- T extends (...args: any[]) => infer R
checks if T
is a function type
— → if T
is function, TS infers the return type of that function as R
— → if T
is NOT function it returns never
It’s enough to implement our utility function that would extract the type T
from interfaceSchema<T>
, that should be implemented by each validator:
Here, we created a condition where check whether the type is the type for our validator interface ( Schema<T>
) and unwrap the generic placeholder T
from it.
Let’s test it:

Isn’t it cool?
Implementing the Object validator
But I know that it is still not impressive. Let’s also create a validator for objects. Probably the most difficult part of this task will be defining the type of placeholder T
in our generic interface Schema<T>
.I usually start using a lot of any
and unknown
types when creating a new difficult type and after that step-by-step unwrapping and specifying each any
or unknown
. The first version may look this way:
The objectValidator
function accepts an object schema as a parameter, where the schema has the type Record<string, Schema<unknown>>
. This schema defines the structure and expected types for each field in the object. The objectValidator
must conform to the Schema
interface and should return a validated object, adhering to the shape Record<string, unknown>
. However, using the unknown
type alone is not very informative for our purposes.

Picture 3 — Inferred type for objectValidator (version 1)
To make this more useful, we need to 'unwrap' the specific types of each field in the schema.
How could we extract the type for each object field here? It makes sense to resolve another simpler, relevant task here. Let’s assume that we have a function that accepts two parameters — key and object. Our task is to extract the object value for the defined key:
But it is useless when this function returns a value with the type any
, Typescript has a mechanism that may prevent a developer from unintentionally passing incorrect types, ultimately leading to a more robust and error-free codebase.
We may introduce a generic type for the function. It brings a bunch of flexibility:
The main advantage of this implementation is that it enforces type safety by ensuring that the key
argument corresponds to an actual key in the given object
. This means you cannot inadvertently pass a key that does not exist within the object’s properties, as TypeScript will issue a compile-time error if you attempt to do so:

But when you try to get value from the object that contains fields with different types, it won’t be what you expect:

It returns a value with the type that is the union of all value types presented in the object. However, our goal is to retrieve a specific type associated with a particular key. To achieve that, we can make a simple adjustment by introducing an additional generic placeholder for the generic type (remember that any generic type may contain as many generic type placeholders as needed):
Here <T extends Record<string, any>, KEY extends keyof T>
we introduced constraints for object T
where KEY
is one of the actual keys of T
. The returned type T[KEY]
now is more specific and returns a type that is associated with the value for the specific key:

Okay, let’s come back to our objectValidator
and try to enhance it by converting the function definition with generic types:
Here, we just created a generic type for function declaration, and nothing special happened in the behavior (compare Snippet 4 and Snippet 5). But we may improve this and define the shape of object for validated value:
Here, we unwrap a little bit a type for returned value in the validator in this way:
Schema<Record<string, unknown>> -> Schema<{[KEY in keyof T]: unknown}>
It just means that the returned type should contain only keys that contain object schema that is passed as an argument for the validator function. Let’s double-check it:

Picture 4 — Inferred type for objectValidator (version 2)
And we need to unwrap the last unknown
— the type for each validated value within an object. However, the type for each value is available by unwrapping the Scheme
interface that contains the parse
method. The returned value of the parse
method is an expected type. Here, we also need to use a built-in Typescript utility ReturnType
that extracts the returned type from the function definition. The final version will be:
Let’s have a look at the result of this enhancement:
Schema<{[KEY in keyof T]: unknown}> -> Schema<{[KEY in keyof T]: ReturnType<T[KEY]['parse']>}>
:

Picture 5— Inferred type for objectValidator (version 3)
Isn’t it cool?
And probably the last step is to define implementation for our parse
method:
Conclusions:
In this article, we partially re-implemented the functionality of the Zod library and show how we may infer the type from the defined schema. We found about the following Typescript advanced features:
- Generic types
- Special unknown
type
- Conditional Type Inferring
- Type guards
- Built-in utility ReturnType
I strongly recommend on your own implement other validators and make your hands dirty. It may be a validator for numbers. Also, it makes sense to practice with a validator that makes a field optional: