Skip to content

Getting Started

type-plus provides over 200 utility types and functions for applications, libraries, and type-level programming.

Terminal window
npm install type-plus

type-plus declares typescript as a peer dependency and supports TypeScript 5.6 and above. See TypeScript version compatibility for the versions covered by the test matrix.

Most of type-plus is types, so nothing needs to be imported at runtime:

import type {
type IsEqual<A, B, Then = true, Else = false> = [A, B] extends [B, A] ? And<$Special<A, {
$any: false;
$unknown: false;
$never: true;
$void: false;
$else: false;
}>, $Special<B, {
$any: false;
$unknown: false;
$never: true;
$void: false;
$else: false;
}>, {
$then: Then;
$else: Or<$Special<A, {
$any: false;
$unknown: false;
$never: true;
$void: false;
$else: false;
}>, $Special<B, {
$any: false;
$unknown: false;
$never: true;
$void: false;
$else: false;
}>, {
$then: Else;
$else: And<$Special<A, {
$any: true;
$unknown: false;
$never: false;
$void: false;
$else: false;
}>, $Special<...>, {
...;
}>;
}>;
}> : And<...>

Checks A and B are equal.

type R = IsEqual<1, 1> // true
type R = IsEqual<any, any> // true
type R = IsEqual<boolean, boolean> // true
type R = IsEqual<true, true> // true
type R = IsEqual<[1], [1]> // true
type R = IsEqual<boolean, true> // false
type R = IsEqual<any, 1> // false
type R = IsEqual<[any], [1]> // false
type R = IsEqual<{ a: 1 }, { a: 1; b: 2 }> // false

Note that intersection type checks only works at first level. It cannot be check recursively, or else will run into infinite recursion if the type includes recursive types.

@deprecated💀 deprecated since 8.0.0: use Equal instead.

IsEqual
} from 'type-plus'
type A =
type IsEqual<A, B, Then = true, Else = false> = [A, B] extends [B, A] ? And<$Special<A, {
$any: false;
$unknown: false;
$never: true;
$void: false;
$else: false;
}>, $Special<B, {
$any: false;
$unknown: false;
$never: true;
$void: false;
$else: false;
}>, {
$then: Then;
$else: Or<$Special<A, {
$any: false;
$unknown: false;
$never: true;
$void: false;
$else: false;
}>, $Special<B, {
$any: false;
$unknown: false;
$never: true;
$void: false;
$else: false;
}>, {
$then: Else;
$else: And<$Special<A, {
$any: true;
$unknown: false;
$never: false;
$void: false;
$else: false;
}>, $Special<...>, {
...;
}>;
}>;
}> : And<...>

Checks A and B are equal.

type R = IsEqual<1, 1> // true
type R = IsEqual<any, any> // true
type R = IsEqual<boolean, boolean> // true
type R = IsEqual<true, true> // true
type R = IsEqual<[1], [1]> // true
type R = IsEqual<boolean, true> // false
type R = IsEqual<any, 1> // false
type R = IsEqual<[any], [1]> // false
type R = IsEqual<{ a: 1 }, { a: 1; b: 2 }> // false

Note that intersection type checks only works at first level. It cannot be check recursively, or else will run into infinite recursion if the type includes recursive types.

@deprecated💀 deprecated since 8.0.0: use Equal instead.

IsEqual
<'a', 'a'>
type A = true
type B =
type IsEqual<A, B, Then = true, Else = false> = [A, B] extends [B, A] ? And<$Special<A, {
$any: false;
$unknown: false;
$never: true;
$void: false;
$else: false;
}>, $Special<B, {
$any: false;
$unknown: false;
$never: true;
$void: false;
$else: false;
}>, {
$then: Then;
$else: Or<$Special<A, {
$any: false;
$unknown: false;
$never: true;
$void: false;
$else: false;
}>, $Special<B, {
$any: false;
$unknown: false;
$never: true;
$void: false;
$else: false;
}>, {
$then: Else;
$else: And<$Special<A, {
$any: true;
$unknown: false;
$never: false;
$void: false;
$else: false;
}>, $Special<...>, {
...;
}>;
}>;
}> : And<...>

Checks A and B are equal.

type R = IsEqual<1, 1> // true
type R = IsEqual<any, any> // true
type R = IsEqual<boolean, boolean> // true
type R = IsEqual<true, true> // true
type R = IsEqual<[1], [1]> // true
type R = IsEqual<boolean, true> // false
type R = IsEqual<any, 1> // false
type R = IsEqual<[any], [1]> // false
type R = IsEqual<{ a: 1 }, { a: 1; b: 2 }> // false

Note that intersection type checks only works at first level. It cannot be check recursively, or else will run into infinite recursion if the type includes recursive types.

@deprecated💀 deprecated since 8.0.0: use Equal instead.

IsEqual
<'a', 'b'>
type B = false

The predicate types share one convention: instead of collapsing to boolean, they let you choose what each branch returns, and how the special types any, never and unknown are treated:

import type {
type IsString<T, $O extends IsString.$Options = {}> = 0 extends 1 & T ? "$any" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>[Exclude<keyof $O, "$then" | "$else"> & "$any"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$unknown" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$unknown"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$never" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$never"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$void" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$void"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : "$else" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>["$else"] : $MergeOptions<...>["selection"] extends "filter" ? never : false

🎭 predicate

Validate if T is string or string literals.

@example

type R = IsString<string> // true
type R = IsString<'a'> // true
type R = IsString<never> // false
type R = IsString<unknown> // false
type R = IsString<string | boolean> // boolean

🔢 customize

Filter to ensure T is string or string literals, otherwise returns never.

@example

type R = IsString<string, { selection: 'filter' }> // string
type R = IsString<'a', { selection: 'filter' }> // 'a'
type R = IsString<never, { selection: 'filter' }> // never
type R = IsString<unknown, { selection: 'filter' }> // never
type R = IsString<string | boolean, { selection: 'filter' }> // string

🔢 customize:

Disable distribution of union types.

type R = IsString<string | 1> // boolean
type R = IsString<string | 1, { distributive: false }> // false

🔢 customize

Use unique branch identifiers to allow precise processing of the result.

@example

type R = IsString<string, IsString.$Branch> // $Then
type R = IsString<bigint, IsString.$Branch> // $Else

IsString
} from 'type-plus'
type A =
type IsString<T, $O extends IsString.$Options = {}> = 0 extends 1 & T ? "$any" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>[Exclude<keyof $O, "$then" | "$else"> & "$any"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$unknown" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$unknown"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$never" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$never"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$void" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$void"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : "$else" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>["$else"] : $MergeOptions<...>["selection"] extends "filter" ? never : false

🎭 predicate

Validate if T is string or string literals.

@example

type R = IsString<string> // true
type R = IsString<'a'> // true
type R = IsString<never> // false
type R = IsString<unknown> // false
type R = IsString<string | boolean> // boolean

🔢 customize

Filter to ensure T is string or string literals, otherwise returns never.

@example

type R = IsString<string, { selection: 'filter' }> // string
type R = IsString<'a', { selection: 'filter' }> // 'a'
type R = IsString<never, { selection: 'filter' }> // never
type R = IsString<unknown, { selection: 'filter' }> // never
type R = IsString<string | boolean, { selection: 'filter' }> // string

🔢 customize:

Disable distribution of union types.

type R = IsString<string | 1> // boolean
type R = IsString<string | 1, { distributive: false }> // false

🔢 customize

Use unique branch identifiers to allow precise processing of the result.

@example

type R = IsString<string, IsString.$Branch> // $Then
type R = IsString<bigint, IsString.$Branch> // $Else

IsString
<string>
type A = true
type B =
type IsString<T, $O extends IsString.$Options = {}> = 0 extends 1 & T ? "$any" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>[Exclude<keyof $O, "$then" | "$else"> & "$any"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$unknown" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$unknown"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$never" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$never"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$void" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$void"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : "$else" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>["$else"] : $MergeOptions<...>["selection"] extends "filter" ? never : false

🎭 predicate

Validate if T is string or string literals.

@example

type R = IsString<string> // true
type R = IsString<'a'> // true
type R = IsString<never> // false
type R = IsString<unknown> // false
type R = IsString<string | boolean> // boolean

🔢 customize

Filter to ensure T is string or string literals, otherwise returns never.

@example

type R = IsString<string, { selection: 'filter' }> // string
type R = IsString<'a', { selection: 'filter' }> // 'a'
type R = IsString<never, { selection: 'filter' }> // never
type R = IsString<unknown, { selection: 'filter' }> // never
type R = IsString<string | boolean, { selection: 'filter' }> // string

🔢 customize:

Disable distribution of union types.

type R = IsString<string | 1> // boolean
type R = IsString<string | 1, { distributive: false }> // false

🔢 customize

Use unique branch identifiers to allow precise processing of the result.

@example

type R = IsString<string, IsString.$Branch> // $Then
type R = IsString<bigint, IsString.$Branch> // $Else

IsString
<number>
type B = false
// Unions distribute, so a mixed union answers `boolean`
type C =
type IsString<T, $O extends IsString.$Options = {}> = 0 extends 1 & T ? "$any" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>[Exclude<keyof $O, "$then" | "$else"> & "$any"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$unknown" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$unknown"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$never" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$never"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$void" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$void"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : "$else" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>["$else"] : $MergeOptions<...>["selection"] extends "filter" ? never : false

🎭 predicate

Validate if T is string or string literals.

@example

type R = IsString<string> // true
type R = IsString<'a'> // true
type R = IsString<never> // false
type R = IsString<unknown> // false
type R = IsString<string | boolean> // boolean

🔢 customize

Filter to ensure T is string or string literals, otherwise returns never.

@example

type R = IsString<string, { selection: 'filter' }> // string
type R = IsString<'a', { selection: 'filter' }> // 'a'
type R = IsString<never, { selection: 'filter' }> // never
type R = IsString<unknown, { selection: 'filter' }> // never
type R = IsString<string | boolean, { selection: 'filter' }> // string

🔢 customize:

Disable distribution of union types.

type R = IsString<string | 1> // boolean
type R = IsString<string | 1, { distributive: false }> // false

🔢 customize

Use unique branch identifiers to allow precise processing of the result.

@example

type R = IsString<string, IsString.$Branch> // $Then
type R = IsString<bigint, IsString.$Branch> // $Else

IsString
<string | 1>
type C = boolean
// The special types get their own branches. `any` answers `false` by default,
type D =
type IsString<T, $O extends IsString.$Options = {}> = 0 extends 1 & T ? "$any" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>[Exclude<keyof $O, "$then" | "$else"> & "$any"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$unknown" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$unknown"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$never" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$never"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$void" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$void"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : "$else" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>["$else"] : $MergeOptions<...>["selection"] extends "filter" ? never : false

🎭 predicate

Validate if T is string or string literals.

@example

type R = IsString<string> // true
type R = IsString<'a'> // true
type R = IsString<never> // false
type R = IsString<unknown> // false
type R = IsString<string | boolean> // boolean

🔢 customize

Filter to ensure T is string or string literals, otherwise returns never.

@example

type R = IsString<string, { selection: 'filter' }> // string
type R = IsString<'a', { selection: 'filter' }> // 'a'
type R = IsString<never, { selection: 'filter' }> // never
type R = IsString<unknown, { selection: 'filter' }> // never
type R = IsString<string | boolean, { selection: 'filter' }> // string

🔢 customize:

Disable distribution of union types.

type R = IsString<string | 1> // boolean
type R = IsString<string | 1, { distributive: false }> // false

🔢 customize

Use unique branch identifiers to allow precise processing of the result.

@example

type R = IsString<string, IsString.$Branch> // $Then
type R = IsString<bigint, IsString.$Branch> // $Else

IsString
<any>
type D = false
// unless you say otherwise
type E =
type IsString<T, $O extends IsString.$Options = {}> = 0 extends 1 & T ? "$any" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>[Exclude<keyof $O, "$then" | "$else"> & "$any"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$unknown" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$unknown"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$never" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$never"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : [...] extends [...] ? "$void" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>[Exclude<...> & "$void"] : "$then" extends "$then" | "$else" | Exclude<keyof $O, "$then" | "$else"> ? $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>["$then"] : $MergeOptions<...>["selection"] extends "filter" ? T : true : "$else" extends "$then" | ... 1 more ... | Exclude<...> ? $MergeOptions<...>["$else"] : $MergeOptions<...>["selection"] extends "filter" ? never : false

🎭 predicate

Validate if T is string or string literals.

@example

type R = IsString<string> // true
type R = IsString<'a'> // true
type R = IsString<never> // false
type R = IsString<unknown> // false
type R = IsString<string | boolean> // boolean

🔢 customize

Filter to ensure T is string or string literals, otherwise returns never.

@example

type R = IsString<string, { selection: 'filter' }> // string
type R = IsString<'a', { selection: 'filter' }> // 'a'
type R = IsString<never, { selection: 'filter' }> // never
type R = IsString<unknown, { selection: 'filter' }> // never
type R = IsString<string | boolean, { selection: 'filter' }> // string

🔢 customize:

Disable distribution of union types.

type R = IsString<string | 1> // boolean
type R = IsString<string | 1, { distributive: false }> // false

🔢 customize

Use unique branch identifiers to allow precise processing of the result.

@example

type R = IsString<string, IsString.$Branch> // $Then
type R = IsString<bigint, IsString.$Branch> // $Else

IsString
<any, {
$any: 1
$any
: 1 }>
type E = 1

This is the single idea that makes the library composable. Read Type branching next, then Options for the full list.

  • API — the utilities, grouped by the type they operate on.
  • Categories — what the icons in the docs mean.
  • Status — how stability and since markers are recorded.