Skip to content

Expressions

Intersection

Like its TypeScript counterpart, an intersection combines two existing Types to create a new Type that enforces the constraints of both.

const 
const myObject: Type<{
    intersected: string;
}>
myObject
=
const type: TypeParser
<{
    readonly intersected: "string.email & /@arktype\\.io$/";
}, Type<{
    intersected: string;
}, {}>>(def: validateObjectLiteral<{
    readonly intersected: "string.email & /@arktype\\.io$/";
}, {}, bindThis<{
    readonly intersected: "string.email & /@arktype\\.io$/";
}>>) => Type<...> (+2 overloads)
type
({
// an email address with the domain arktype.io intersected: "string.email & /@arktype\\.io$/" })

Union

All unions are automatically discriminated to optimize check time and error message clarity.

const 
const unions: Type<{
    key: string | number;
}>
unions
=
const type: TypeParser
<{
    readonly key: "string | number";
}, Type<{
    key: string | number;
}, {}>>(def: validateObjectLiteral<{
    readonly key: "string | number";
}, {}, bindThis<{
    readonly key: "string | number";
}>>) => Type<...> (+2 overloads)
type
({
key: "string | number" })

Brand

Add a type-only symbol to an existing type so that the only values that satisfy is are those that have been directly validated.

// hover to see ArkType's representation of a branded type
const 
const myObject: Type<{
    brandedString: Brand<string, "id">;
}>
myObject
=
const type: TypeParser
<{
    readonly brandedString: "string#id";
}, Type<{
    brandedString: Brand<string, "id">;
}, {}>>(def: validateObjectLiteral<{
    readonly brandedString: "string#id";
}, {}, bindThis<{
    readonly brandedString: "string#id";
}>>) => Type<...> (+2 overloads)
type
({
brandedString: "string#id" })

Narrow

Narrow expressions allow you to add custom validation logic and error messages. You can read more about them in their intro section.

const 
const form: Type<{
    password: string;
    confirmPassword: string;
}>
form
=
const type: TypeParser
<{
    readonly password: "string";
    readonly confirmPassword: "string";
}, Type<{
    password: string;
    confirmPassword: string;
}, {}>>(def: validateObjectLiteral<{
    readonly password: "string";
    readonly confirmPassword: "string";
}, {}, bindThis<...>>) => Type<...> (+2 overloads)
type
({
password: "string", confirmPassword: "string" }).narrow((data, ctx) => { if (data.password === data.confirmPassword) { return true } return ctx.reject({ expected: "identical to password", // don't display the password in the error message! actual: "", path: ["confirmPassword"] }) }) // ArkErrors: confirmPassword must be identical to password const
const out: {
    password: string;
    confirmPassword: string;
} | ArkErrors
out
= form({
password: "arktype", confirmPassword: "artkype" })

If the return type of a narrow is a type predicate, that will be reflected in the inferred Type.

// hover to see how the predicate is propagated to the outer `Type`
const const arkString: Type<`ark${string}`>arkString = 
const type: TypeParser
<"string", Type<string, {}>>(def: "string") => Type<string, {}> (+2 overloads)
type
("string").narrow(
(data, ctx): data is `ark${string}` => data.startsWith("ark") ?? ctx.reject("a string starting with 'ark'") )

Morph

Morphs allow you to transform your data after it is validated. You can read more about them in their intro section.

// hover to see how morphs are represented at a type-level
const const trimStringStart: Type<(In: string) => Out<string>>trimStringStart = 
const type: TypeParser
<"string", Type<string, {}>>(def: "string") => Type<string, {}> (+2 overloads)
type
("string").pipe(str => str.trimStart())

Unit

While embedded literal syntax is usually ideal for defining exact primitive values, === and type.unit can be helpful for referencing a non-serialiazable value like a symbol from your type.

const const mySymbol: typeof mySymbolmySymbol = Symbol()

const const exactValue: Type<typeof mySymbol>exactValue = type.unit(const mySymbol: typeof mySymbolmySymbol)

Enumerated

type.enumerated defines a type based on a list of allowed values. It is semantically equivalent to type.unit if provided a single value.

const const mySymbol: typeof mySymbolmySymbol = Symbol()

const const exactValueFromSet: Type<true | typeof mySymbol | 1337>exactValueFromSet = type.enumerated(1337, true, const mySymbol: typeof mySymbolmySymbol)

Meta

Metadata allows you to associate arbitrary metadata with your types.

Some metadata is consumed directly by ArkType, for example description is referenced by default when building an error message.

Other properties are introspectable, but aren’t used by default internally.

// this validator's error message will now start with "must be a special string"
const const specialString: Type<string>specialString = 
const type: TypeParser
<"string", Type<string, {}>>(def: "string") => Type<string, {}> (+2 overloads)
type
("string").configure({
description: "a special string" }) // sugar for adding description metadata const const specialNumber: Type<number>specialNumber =
const type: TypeParser
<"number", Type<number, {}>>(def: "number") => Type<number, {}> (+2 overloads)
type
("number").describe("a special number")

Cast

Sometimes, you may want to directly specify how a Type should be inferred without affecting the runtime behavior. In these cases, you can use a cast expression.

// allow any string, but suggest "foo" and "bar"
type type AutocompletedString = "foo" | "bar" | (string & {})AutocompletedString = "foo" | "bar" | (string & {})

const 
const myObject: Type<{
    autocompletedString: AutocompletedString;
}>
myObject
=
const type: TypeParser
<{
    readonly autocompletedString: type.cast<AutocompletedString>;
}, Type<{
    autocompletedString: AutocompletedString;
}, {}>>(def: validateObjectLiteral<{
    readonly autocompletedString: type.cast<AutocompletedString>;
}, {}, bindThis<...>>) => Type<...> (+2 overloads)
type
({
autocompletedString: "string" as type.cast<type AutocompletedString = "foo" | "bar" | (string & {})AutocompletedString> })

Parenthetical

By default, ArkType’s operators follow the same precedence as TypeScript’s. Also like in TypeScript, this can be overridden by wrapping an expression in parentheses.

// hover to see the distinction!
const 
const groups: Type<{
    stringOrArrayOfNumbers: string | number[];
    arrayOfStringsOrNumbers: (string | number)[];
}>
groups
=
const type: TypeParser
<{
    readonly stringOrArrayOfNumbers: "string | number[]";
    readonly arrayOfStringsOrNumbers: "(string | number)[]";
}, Type<{
    stringOrArrayOfNumbers: string | number[];
    arrayOfStringsOrNumbers: (string | number)[];
}, {}>>(def: validateObjectLiteral<...>) => Type<...> (+2 overloads)
type
({
stringOrArrayOfNumbers: "string | number[]", arrayOfStringsOrNumbers: "(string | number)[]" })

this

this is a special keyword that can be used to create a recursive type referencing the root of the current definition.

const 
const disappointingGift: Type<{
    label: string;
    box?: ...;
}>
disappointingGift
=
const type: TypeParser
<{
    readonly label: "string";
    readonly "box?": "this";
}, Type<{
    label: string;
    box?: ...;
}, {}>>(def: validateObjectLiteral<{
    readonly label: "string";
    readonly "box?": "this";
}, {}, bindThis<{
    readonly label: "string";
    readonly "box?": "this";
}>>) => Type<...> (+2 overloads)
type
({ label: "string", "box?": "this" })
const
const myGift: {
    label: string;
    box?: ...;
}
myGift
=
const disappointingGift: Type<{
    label: string;
    box?: ...;
}>
disappointingGift
.assert(await const fetchGift: () => Promise<null>fetchGift())
// hover me const const chainable: string | undefinedchainable =
const myGift: {
    label: string;
    box?: ...;
}
myGift
.box?.box?.label

Unlike its TypeScript counterpart, ArkType’s this is not limited to interfaces. It can also be used from within a tuple expression.

// boxes now expects an array of our gift object
const 
const disappointingGifts: Type<{
    label: string;
    boxes: ...[];
}[]>
disappointingGifts
=
const type: TypeParser
<{
    readonly label: "string";
    readonly boxes: "this";
}, "[]", readonly [], Type<{
    label: string;
    boxes: ...[];
}[], {}>>(_0: validateObjectLiteral<{
    readonly label: "string";
    readonly boxes: "this";
}, {}, bindThis<{
    readonly label: "string";
    readonly boxes: "this";
}>>, _1: "[]") => Type<...> (+2 overloads)
type
({ label: "string", boxes: "this" }, "[]")

Referencing this from within a scope will result in a ParseError. For similar behavior within a scoped definition, just reference the alias by name:

const 
const types: Module<{
    disappointingGift: {
        label: string;
        box?: ...;
    };
}>
types
=
const scope: ScopeParser
<{
    readonly disappointingGift: {
        readonly label: "string";
        readonly "box?": "disappointingGift";
    };
}>(def: scope.validate<{
    readonly disappointingGift: {
        readonly label: "string";
        readonly "box?": "disappointingGift";
    };
}>, config?: ArkScopeConfig) => Scope<...>
scope
({
disappointingGift: { label: "string", // Resolves correctly to the root of the current type "box?": "disappointingGift" } }).export()