(value: unknown) => value is T, so it narrows types in if blocks. Each property access or method call returns a new guard.
The is object
Import is from ts-chas/guard. It is the single entry point for all built-in guards.
is is itself a guard (or a factory that produces one). You chain helpers by accessing properties or calling methods directly on the guard:
How chaining works
Each step in a chain applies left to right. Transformers (like.trim()) mutate the value flowing through the chain, while validators (like .email) refine it.
Universal helpers
Every guard, regardless of type, exposes the following methods:.parse(value)
Validates value and returns Result<T, GuardErr>. If the guard has a transform pipeline (e.g., .trim()), the transformed value is returned on success.
.assert(value)
Like .parse(), but throws a GuardErr on failure instead of returning an Err. Returns the typed value on success.
.error(msg) / .error(fn)
Overrides the default error message. Accepts a static string or a function receiving { meta, value }.
.nullable
Widens the guard to also accept null. Returns Guard<T | null>.
.optional
Widens the guard to also accept undefined. Returns Guard<T | undefined>.
.nullish
Widens the guard to also accept null or undefined. Returns Guard<T | null | undefined>.
.and(otherGuard)
Logical AND: both guards must pass. The value is typed as T & U.
.or(otherGuard)
Logical OR: either guard can pass. The value is typed as T | U. Type-specific helpers are dropped on the result since the type is now a union.
.where(predicate)
Adds a custom inline validation rule. The predicate receives the (possibly transformed) value.
.brand(tag)
Adds a compile-time brand to the output type. Has no runtime effect — use it to distinguish semantically different values of the same primitive type.
.fallback(value)
Sets a fallback value returned by .parse() and .assert() when validation fails, instead of producing an error. Does not affect the boolean type predicate.
.transform(fn)
Applies a type-changing transformation to the validated value. The guard still validates the original input; .parse() / .assert() return the transformed value. Drops type-specific helpers since the output type may differ.
.refine(fn)
Like .transform(), but the output type stays T and type-specific helpers are preserved.
.not
Inverts the guard. Passes when the original fails; typed as Guard<unknown>.
.array
Wraps the guard as an element guard for arrays. Equivalent to is.array(thisGuard).
.coerce
Adds coercion support to the guard. When enabled, the guard attempts to cast “loose” inputs (like numeric strings or truthy values) into the target type before validation.
Coercion happens during.parse(),.assert(), and Standard Schema validation.
.arbitrary()
Returns a Promise that resolves to a fast-check Arbitrary<T> for the guard. Requires fast-check to be installed (npm install fast-check). The arbitrary reflects all constraints accumulated through the helper chain.
.generate(n?)
Generates n valid values that satisfy this guard (default: 1, returns a single value). Requires fast-check to be installed (npm install fast-check). Generated values are guaranteed to pass the guard’s predicate.
.toJsonSchema()
Serializes the guard to a JSON Schema Draft-07 compatible object. Captures constraints accumulated through the helper chain (min/max/email/etc.), recursively resolves object shapes and array element types, and handles nullable/optional variants.
Best-effort: exotic guards (lazy, custom functions) fall back to {}.
.whereAsync(predicate)
Appends an async predicate check, switching to async mode. The returned AsyncGuard<T> has .parseAsync() returning a ResultAsync<T, GuardErr> with the full monadic API.
.refineAsync(fn)
Appends an async same-type transformation, switching to async mode. The resolved value replaces the current value and is passed to subsequent steps.
.transformAsync(fn)
Appends an async type-changing transformation, switching to async mode.
Type coercion
.coerce is available on guards for the core coercible types: string, number, boolean, date, bigint, object, array, and result. It adds an automatic conversion step that runs before validation whenever .parse(), .assert(), or Standard Schema validation is called.
If the value already satisfies the guard without coercion, it passes through unchanged. If coercion produces a value that still fails the guard, the result is an ordinary validation error.
.coerce run against the coerced value:
guard(v)). Calling a .coerce guard as a plain predicate returns true if the value is coercible — but the runtime value is not converted. Use .parse() or .assert() to get the transformed value.
Nested guards compose naturally: if an object’s fields also use .coerce, inner coercions run after the outer one resolves the object:
Test data generation
.generate() and .arbitrary() let you produce valid test data from the same guard that validates it. No separate factory functions or schemas to maintain.
Both require fast-check as a peer dependency:
.generate(n?)
Returns a single valid value (Promise<T>) or an array of n valid values (Promise<T[]>). Every generated value is guaranteed to pass the guard’s predicate.
.generate(n) and feeding it to it.each:
.arbitrary()
Returns a Promise<Arbitrary<T>> — a fully configured fast-check Arbitrary. Use it for property-based tests with fc.assert and fc.property, or compose it with .map(), .filter(), and .chain():
is.number.int.positive.lte(100) produces integers in [1, 100]. Guards that cannot be mapped to a specific arbitrary fall back to fc.anything(). For the full constraint coverage reference, see the generate and arbitrary guide.
Async validation
When a validation step requires I/O, use.whereAsync(), .refineAsync(), or .transformAsync() to switch a guard into async mode. These return an AsyncGuard<T>, which is not a Guard — it does not have .parse() or the helper chain. Its only output is .parseAsync().
Entering async mode
Any of the three methods can start the async chain. Sync helpers added to the base guard before the async call run first:.whereAsync(fn) — async predicate
Resolving false fails validation. Subsequent steps are not called if a predicate fails.
.refineAsync(fn) — async same-type transform
The resolved value replaces the current value. The type stays T.
.transformAsync(fn) — async type-changing transform
The return type of fn becomes the new type of the AsyncGuard<U>.
.parseAsync(value, errMsg?)
Runs the full chain and returns ResultAsync<T, GuardErr>. The sync guard runs first; async steps are skipped entirely on sync failure.
ResultAsync exposes the full monadic API (.map(), .andThen(), .match(), .unwrap()):
Standard Schema (~standard)
Every guard implements the Standard Schema v1 specification via the ~standard property. This makes guards compatible with tRPC, react-hook-form, Drizzle, and any other library that consumes the spec — no adapter needed.
Extending the is namespace
Use is.extend({ ... }) to add custom guards to a new is instance. The base guards remain available on the returned object.
extend — it performs a shallow merge with baseIs.
Type utilities
InferGuard<T>
Extracts the validated type from a guard.
.$infer property: