Configuration

A great out-of-the-box experience is a core goal of ArkType, including safe defaults and helpful messages for complex errors.

However, it's equally important that when you need different behavior, you can easily configure it with the right granularity.

LevelApplies ToExample
defaultbuilt-in defaults for all Types
globalall Types parsed after the config is applied
import { configure } from "arktype/config"
// importing from "arktype/config" first is recommended (see note)
configure({ numberAllowsNaN: true })

import { type } from "arktype"
// true
type.number.allows(Number.NaN) 
scopeall Types parsed in the configured Scope
const  = (
	{ user: {  name: "string" } },
	{ onUndeclaredKey: "reject" }
)
const  = .export()
// ArkErrors: age must be removed
.user({ name: "Alice", age: 99 })
const  = .type({
  name: "string"
})
// ArkErrors: age must be removed
parsedAfter({ name: "Alice", age: 99 })
typeall Types shallowly referenced by the configured Type
// avoid logging "was xxx" for password
const  = ("string >= 8").configure({ actual: () => "" })
const  = ({
	email: "string.email",
	password
})
// ArkErrors: password must be at least length 8
const  = user({
	email: "david@arktype.io",
	password: "ez123"
})

Some options only apply at specific levels, as reflected in the corresponding input types.

Use the `"arktype/config"` entrypoint if for global config!

If you need your config to apply to built-in keywords (important for options like jitless, numberAllowsNaN, dateAllowsInvalid), you should import and configure from "arktype/config" before importing anything from "arktype".

Otherwise, keywords will have already been parsed by the time your config applies!

Errors

To allow custom errors to be integrated seemlessly with built-in logic for composite errors (i.e. union and intersection), ArkType supports a set of composable options:

optionaldescriptionexample
description

✅ a summary of the constraint that could complete the phrase "must be ___"

🥇 reused by other metadata and should be your first go-to for customizing a message

const  = type.string.atLeastLength(8).describe("a valid password")
// ArkErrors: must be a valid password
const  = password("ez123")
expected

✅ a function accepting the error context and returning a string of the format "must be ___"

✅ specific to errors and takes precedence over description in those cases

const  = type.string.atLeastLength(8).configure({
	expected: ctx =>
		ctx.code === "minLength" ? `${ctx.rule} characters or better` : "way better"
})
// ArkErrors: must be 8 characters or better (was 5)
const  = password("ez123").toString()
// ArkErrors: must be way better (was a number)
const  = password(12345678).toString()
actual

✅ a function accepting the data that caused the error and returning a string of the format "(was ___)"

✅ if an empty string is returned, the actual portion of the message will be omitted

const  = ("string >= 8").configure({ actual: () => "" })
// ArkErrors: password must be at least length 8
const  = password("ez123")
problem

✅ a function accepting the results of expected and actual in addition to other context and returning a complete description of the problem like "must be a string (was a number)"

❌ may not apply to composite errors like unions

const  = ("string >= 8").configure({
	problem: ctx => `${ctx.actual} isn't ${ctx.expected}`
})
// ArkErrors: 5 isn't at least length 8
const  = password("ez123")
// ArkErrors: a number isn't a string
const  = password(12345678)
message

✅ a function accepting the result of problem in addition to other context and returning a complete description of the problem including the path at which it occurred

❌ may not apply to composite errors like unions

const  = ({
	password: "string >= 8"
}).configure({
	message: ctx =>
		`${ctx.propString || "(root)"}: ${ctx.actual} isn't ${ctx.expected}`
})
// ArkErrors: (root): a string isn't an object
const  = user("ez123")
// `.configure` only applies shallowly, so the nested error isn't changed!
// ArkErrors: password must be at least length 8 (was 5)
const  = user({ password: "ez123" })

By Code

Errors can also be configured by their associated code property at a scope or global level.

For example:

const  = type.module(
	{ isEven: "number%2" },
	{
		divisor: {
			// the available `ctx` types will include data specific to your errors
			expected: ctx => `% ${ctx.rule} !== 0`,
			problem: ctx => `${ctx.actual} ${ctx.expected}`
		}
	}
)
// ArkErrors: 3 % 2 !== 0
.isEven(3)

ArkErrors

For use cases like i18n that fall outside the scope of this composable message config, the ArkErrors array returned on validation failure contains ArkError instances that can be discriminated via calls like .hasCode("divisor") and contain contextual data specific to that error type as well as getters for each composable error part.

These ArkError instances can be arbitrarily transformed and composed with an internationalization library. This is still a topic we're working on investigating and documenting, so please reach out with any questions or feedback!

Clone

By default, before a morph is applied, ArkType will deeply clone the original input value with a built-in deepClone function that tries to make reasonable assumptions about preserving prototypes etc. The implementation of deepClone can be found here.

You can provide an alternate clone implementation to the clone config option.

import { configure } from "arktype/config"

configure({ clone: structuredClone })

import { type } from "arktype"

// will now create a new object using structuredClone
const  = type({
	age: "string.numeric.parse"
})

To mutate the input object directly, you can set the clone config option to false.

import { configure } from "arktype/config"

configure({ clone: false })

import { type } from "arktype"

const  = type({
	age: "string.numeric.parse"
})

const  = {
	age: "42"
}

const  = userForm()

// the original object's age key is now a number
console.log(.age)

onUndeclaredKey

Like TypeScript, ArkType defaults to ignoring undeclared keys during validation. However, it also supports two additional behaviors:

  • "ignore" (default): Allow undeclared keys on input, preserve them on output
  • "delete": Allow undeclared keys on input, delete them before returning output
  • "reject": Reject input with undeclared keys

These behaviors can be associated with individual Types via the builtin "+" syntax (see those docs for more on how they work). You can also change the default globally:

import { configure } from "arktype/config"

configure({ onUndeclaredKey: "delete" })

import { type } from "arktype"

const  = type({
	name: "string"
})

// out is now { name: "Alice" }
const  = userForm({
	name: "Alice",
	age: "42"
})

jitless

By default, when a Type is instantiated, ArkType will precompile optimized validation logic that will run when the type is invoked. This behavior is disabled by default in environments that don't support new Function, e.g. Cloudflare Workers.

If you'd like to opt out of it for another reason, you can set the jitless config option to true.

import { configure } from "arktype/config"

configure({ jitless: true })

import { type } from "arktype"

// will not be precompiled
const  = type({
	foo: "string"
})

custom

// add this anywhere in your project
declare global {
	interface ArkEnv {
		meta(): {
			// meta properties should always be optional
			secretIngredient?: string
		}
	}
}

// now types you define can specify and access your metadata
const  = ({
	broth: "'miso' | 'vegetable'",
	ingredients: "string[]"
}).configure({ secretIngredient: "nothing!" })