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 an optional peer dependency and supports TypeScript 5.4 and above. Package managers do not install it for you, so a project that only calls the runtime functions does not pull in a compiler. 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, $O extends $StrictOptions<$O, IsEqual.$Options> = {}> = [A, B] extends [B, A] ? And<0 extends 1 & A ? false : [A] extends [never] ? true : false, 0 extends 1 & B ? false : [B] extends [never] ? true : false, {
$then: "$then" extends keyof $O ? $O[keyof $O & "$then"] : $O["selection"] extends "filter" ? A : true;
$else: Or<0 extends 1 & A ? false : [A] extends [never] ? true : false, 0 extends 1 & B ? false : [B] extends [never] ? true : false, {
$then: "$else" extends keyof $O ? $O[keyof $O & "$else"] : $O["selection"] extends "filter" ? never : false;
$else: And<...>;
}>;
}> : "$else" extends keyof $O ? $O[keyof $O & "$else"] : $O["selection"] extends "filter" ? never : false

🎭 predicate

Validate A and B are equal.

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.

@example

type R = IsEqual<1, 1> // true
type R = IsEqual<any, any> // true
type R = IsEqual<boolean, boolean> // 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
type R = IsEqual<never, undefined> // false
type R = IsEqual<string | undefined, undefined> // false

symbol gets no special treatment: two distinct unique symbols are not equal, and a unique symbol is not equal to symbol. Before 8.0.0, IsEqual reported both of those as equal.

@example

declare const s1: unique symbol
declare const s2: unique symbol
type R = IsEqual<typeof s1, typeof s1> // true
type R = IsEqual<typeof s1, typeof s2> // false
type R = IsEqual<typeof s1, symbol> // false

🔢 customize

Filter to ensure A equals B, otherwise returns never.

@example

type R = IsEqual<1, 1, { selection: 'filter' }> // 1
type R = IsEqual<1, number, { selection: 'filter' }> // never

🔢 customize

Override the branches directly.

@example

type R = IsEqual<1, 1, { $then: 'yes'; $else: 'no' }> // 'yes'
type R = IsEqual<1, 2, { $then: 'yes'; $else: 'no' }> // 'no'

🔢 customize

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

@example

type R = IsEqual<undefined, undefined, IsEqual.$Branch> // $Then
type R = IsEqual<string, undefined, IsEqual.$Branch> // $Else

IsEqual
} from 'type-plus'
type A =
type IsEqual<A, B, $O extends $StrictOptions<$O, IsEqual.$Options> = {}> = [A, B] extends [B, A] ? And<0 extends 1 & A ? false : [A] extends [never] ? true : false, 0 extends 1 & B ? false : [B] extends [never] ? true : false, {
$then: "$then" extends keyof $O ? $O[keyof $O & "$then"] : $O["selection"] extends "filter" ? A : true;
$else: Or<0 extends 1 & A ? false : [A] extends [never] ? true : false, 0 extends 1 & B ? false : [B] extends [never] ? true : false, {
$then: "$else" extends keyof $O ? $O[keyof $O & "$else"] : $O["selection"] extends "filter" ? never : false;
$else: And<...>;
}>;
}> : "$else" extends keyof $O ? $O[keyof $O & "$else"] : $O["selection"] extends "filter" ? never : false

🎭 predicate

Validate A and B are equal.

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.

@example

type R = IsEqual<1, 1> // true
type R = IsEqual<any, any> // true
type R = IsEqual<boolean, boolean> // 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
type R = IsEqual<never, undefined> // false
type R = IsEqual<string | undefined, undefined> // false

symbol gets no special treatment: two distinct unique symbols are not equal, and a unique symbol is not equal to symbol. Before 8.0.0, IsEqual reported both of those as equal.

@example

declare const s1: unique symbol
declare const s2: unique symbol
type R = IsEqual<typeof s1, typeof s1> // true
type R = IsEqual<typeof s1, typeof s2> // false
type R = IsEqual<typeof s1, symbol> // false

🔢 customize

Filter to ensure A equals B, otherwise returns never.

@example

type R = IsEqual<1, 1, { selection: 'filter' }> // 1
type R = IsEqual<1, number, { selection: 'filter' }> // never

🔢 customize

Override the branches directly.

@example

type R = IsEqual<1, 1, { $then: 'yes'; $else: 'no' }> // 'yes'
type R = IsEqual<1, 2, { $then: 'yes'; $else: 'no' }> // 'no'

🔢 customize

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

@example

type R = IsEqual<undefined, undefined, IsEqual.$Branch> // $Then
type R = IsEqual<string, undefined, IsEqual.$Branch> // $Else

IsEqual
<'a', 'a'>
type A = true
type B =
type IsEqual<A, B, $O extends $StrictOptions<$O, IsEqual.$Options> = {}> = [A, B] extends [B, A] ? And<0 extends 1 & A ? false : [A] extends [never] ? true : false, 0 extends 1 & B ? false : [B] extends [never] ? true : false, {
$then: "$then" extends keyof $O ? $O[keyof $O & "$then"] : $O["selection"] extends "filter" ? A : true;
$else: Or<0 extends 1 & A ? false : [A] extends [never] ? true : false, 0 extends 1 & B ? false : [B] extends [never] ? true : false, {
$then: "$else" extends keyof $O ? $O[keyof $O & "$else"] : $O["selection"] extends "filter" ? never : false;
$else: And<...>;
}>;
}> : "$else" extends keyof $O ? $O[keyof $O & "$else"] : $O["selection"] extends "filter" ? never : false

🎭 predicate

Validate A and B are equal.

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.

@example

type R = IsEqual<1, 1> // true
type R = IsEqual<any, any> // true
type R = IsEqual<boolean, boolean> // 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
type R = IsEqual<never, undefined> // false
type R = IsEqual<string | undefined, undefined> // false

symbol gets no special treatment: two distinct unique symbols are not equal, and a unique symbol is not equal to symbol. Before 8.0.0, IsEqual reported both of those as equal.

@example

declare const s1: unique symbol
declare const s2: unique symbol
type R = IsEqual<typeof s1, typeof s1> // true
type R = IsEqual<typeof s1, typeof s2> // false
type R = IsEqual<typeof s1, symbol> // false

🔢 customize

Filter to ensure A equals B, otherwise returns never.

@example

type R = IsEqual<1, 1, { selection: 'filter' }> // 1
type R = IsEqual<1, number, { selection: 'filter' }> // never

🔢 customize

Override the branches directly.

@example

type R = IsEqual<1, 1, { $then: 'yes'; $else: 'no' }> // 'yes'
type R = IsEqual<1, 2, { $then: 'yes'; $else: 'no' }> // 'no'

🔢 customize

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

@example

type R = IsEqual<undefined, undefined, IsEqual.$Branch> // $Then
type R = IsEqual<string, undefined, IsEqual.$Branch> // $Else

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 $StrictOptions<$O, IsString.$Options> = {}> = [keyof $O] extends [never] ? $Special.Values<T, {
$any: false;
$unknown: false;
$never: false;
$void: false;
$else: T extends string ? true : false;
}> : $Special<T, $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>>

🎭 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

Without options, it answers through $Special.Values, skipping the options machinery, which costs a fraction of the instantiations. The spec pins that shortcut to the full path.

IsString
} from 'type-plus'
type A =
type IsString<T, $O extends $StrictOptions<$O, IsString.$Options> = {}> = [keyof $O] extends [never] ? $Special.Values<T, {
$any: false;
$unknown: false;
$never: false;
$void: false;
$else: T extends string ? true : false;
}> : $Special<T, $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>>

🎭 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

Without options, it answers through $Special.Values, skipping the options machinery, which costs a fraction of the instantiations. The spec pins that shortcut to the full path.

IsString
<string>
type A = true
type B =
type IsString<T, $O extends $StrictOptions<$O, IsString.$Options> = {}> = [keyof $O] extends [never] ? $Special.Values<T, {
$any: false;
$unknown: false;
$never: false;
$void: false;
$else: T extends string ? true : false;
}> : $Special<T, $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>>

🎭 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

Without options, it answers through $Special.Values, skipping the options machinery, which costs a fraction of the instantiations. The spec pins that shortcut to the full path.

IsString
<number>
type B = false
// Unions distribute, so a mixed union answers `boolean`
type C =
type IsString<T, $O extends $StrictOptions<$O, IsString.$Options> = {}> = [keyof $O] extends [never] ? $Special.Values<T, {
$any: false;
$unknown: false;
$never: false;
$void: false;
$else: T extends string ? true : false;
}> : $Special<T, $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>>

🎭 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

Without options, it answers through $Special.Values, skipping the options machinery, which costs a fraction of the instantiations. The spec pins that shortcut to the full path.

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 $StrictOptions<$O, IsString.$Options> = {}> = [keyof $O] extends [never] ? $Special.Values<T, {
$any: false;
$unknown: false;
$never: false;
$void: false;
$else: T extends string ? true : false;
}> : $Special<T, $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>>

🎭 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

Without options, it answers through $Special.Values, skipping the options machinery, which costs a fraction of the instantiations. The spec pins that shortcut to the full path.

IsString
<any>
type D = false
// unless you say otherwise
type E =
type IsString<T, $O extends $StrictOptions<$O, IsString.$Options> = {}> = [keyof $O] extends [never] ? $Special.Values<T, {
$any: false;
$unknown: false;
$never: false;
$void: false;
$else: T extends string ? true : false;
}> : $Special<T, $MergeOptions<$O, {
$then: $ResolveBranch<$O, [$Else]>;
$else: IsString.$<T, $O>;
}>>

🎭 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

Without options, it answers through $Special.Values, skipping the options machinery, which costs a fraction of the instantiations. The spec pins that shortcut to the full path.

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.