This document covers upgrading from TypeBox 0.34.x to 1.0. While most of the API surface remains intact for 1.0, there are several key breaking changes that will impact users who integrate deeply with TypeBox infrastructure. The sections below document these changes and detail strategies for upgrading.
- ESM Package
- Kind, Hint, ReadonlyKind and OptionalKind
- Type.Date and Type.Uint8Array
- Type.Recursive
- Type.RegExp
- Type.Composite
- Type.Transform
- Type.Const
- Type.Pick and Type.Omit
- TypeCompiler
- References
- FormatRegistry
- TypeRegistry
- TypeGuard
- Value.Errors
- Value.Cast
- SetErrorFunction
- CustomErrors
Version 1.0 is published as an ESM-only package. Users will need to ensure their environments support ESM module resolution.
Version 1.0 removes the Kind, ReadonlyKind, OptionalKind symbols. These symbols were originally introduced to enable fast type differentiation internal to the library, as well as to solve integration issues with Ajv strict mode.
The use of symbols have proven difficult for users who install multiple versions of TypeBox side by side. This is most notable in frameworks that install one version of TypeBox and where users install another. The removal of symbols will enable multiple version of TypeBox to run side by side without requiring users to pin specific versions via package.json.
Version 1.0 replaces each symbol with non-enumerable ~kind, ~readonly and ~optional properties. Applications that use symbols to reflect TypeBox types will need to update to read these properties instead.
0.34.x
import { Type, Kind } from '@sinclair/typebox'
const T = Type.String()
if(T[Kind] === 'String') { ... }1.0.0
import Type from 'typebox'
const T = Type.String()
if(T['~kind'] === 'String') { ... }
// Consider using guards instead of direct property access.
if(Type.IsString(T)) { ... }Version 1.0 removes the Date and Uint8Array types. These types can be created at an application level by extending Type.Base.
The following implements Uint8Array using Type.Base.
import Type from 'typebox'
export class TUint8Array extends Type.Base<Uint8Array> {
// required: Used by validation
public override Check(value: unknown): value is Uint8Array {
return value instanceof Uint8Array
}
// required: Used by validation
public override Errors(value: unknown): object[] {
return !this.Check(value) ? [{ message: 'not a Uint8Array'}] : []
}
// required: Used by type compositor
public override Clone(): TUint8Array {
return new TUint8Array()
}
}
export function Uint8Array(): TUint8Array {
return new TUint8Array()
}Version 1.0 removes the Recursive type. This type has been replaced with a new Cyclic type which supports both singular and mutual recursive types.
0.34.x
const Node = Type.Recursive(This => Type.Object({
id: Type.Number(),
nodes: Type.Array(This)
}))1.0.0
const Node = Type.Cyclic({
Node: Type.Object({
id: Type.Number(),
nodes: Type.Array(Type.Ref('Node'))
})
}, 'Node')Version 1.0 removes the RegExp type. TypeBox now allows regular expressions to be passed via pattern on String types.
0.34.x
const T = Type.RegExp(/abc/i)1.0.0
const T = Type.String({ pattern: /abc/i })Version 1.0 removes the Composite type. This type has been replaced by the Interface and Evaluate types.
0.34.x
const A = Type.Object({ a: Type.Number() })
const B = Type.Object({ b: Type.Number() })
const C = Type.Composite([A, B])1.0.0
// Composite with Interface and Heritage
const A = Type.Object({ a: Type.Number() })
const B = Type.Object({ b: Type.Number() })
const C = Type.Interface([A, B], { c: Type.Number() }) // const C: TObject<{
// a: TNumber,
// b: TNumber,
// c: TNumber
// }>
// Composite with Evaluate
const A = Type.Object({ a: Type.Number() })
const B = Type.Object({ b: Type.Number() })
const C = Type.Object({ c: Type.Number() })
const T = Type.Evaluate(Type.Intersect([A, B, C])) // const T: TObject<{
// a: TNumber,
// b: TNumber,
// c: TNumber
// }> The Transform type has been renamed to Codec. This change better reflects the distinction between bi-directional vs uni-directional transformation; where 1.0 adds support for uni-directional transforms.
0.34.x
const NumberToString = Type.Transform(Type.Number())
.Decode(value => value.toString())
.Encode(value => parseFloat(value))1.0.0
// Bi-Directional
const NumberToString = Type.Codec(Type.Number())
.Decode(value => value.toString())
.Encode(value => parseFloat(value))
// Uni-Directional
const NumberToStringDecode = Type.Decode(Type.Number(), value => value.toString())
const NumberToStringEncode = Type.Encode(Type.Number(), (value: number) => value.toString())The Const type has been removed in 1.0. This type was originally used to create structural type representations from constant TypeScript values. 1.0 deprecates this functionality in favor of Script which can parse values into structural representations.
0.34.x
const T = Type.Const({ x: 1, y: 2, z: 3 } as const) // const T: TObject<{
// TReadonly<TLiteral<1>>
// TReadonly<TLiteral<2>>
// TReadonly<TLiteral<3>>
// }>1.0.0
const T = Type.Script(`{ x: 1, y: 2, z: 3 }`) // const T: TObject<{
// x: TLiteral<1>;
// y: TLiteral<2>;
// z: TLiteral<3>;
// }>
// Optional: If readonly is required.
const S = Type.Script({ T }, `{
readonly [K in keyof T]: T[K]
}`) // const S: TObject<{
// x: TReadonly<TLiteral<1>>;
// y: TReadonly<TLiteral<2>>;
// z: TReadonly<TLiteral<3>>;
// }>Version 1.0 updates Pick and Omit to return evaluated Object types when applied to Union and Intersect types. This change aligns with V1’s ability to evaluate logical type expressions for mapped types. As a consequence, the return structures of Pick and Omit may vary depending on the source type being used.
In 0.34.x, Pick and Omit would simply traverse a logical type expression and select properties from embedded Object types. In 1.0, Pick and Omit evaluate the result into a normalized Object type. Both 0.34.x and 1.0 return semantically equivalent types, but in 1.0 the result is reduced to its simplest form.
0.34.x
const X = Type.Object({ x: Type.Number(), z: Type.Number() })
const Y = Type.Object({ y: Type.Number(), z: Type.Literal(1) })
const Z = Type.Intersect([X, Y])
const T = Type.Pick(Z, ['z']) // const T: TIntersect<[TObject<{
// z: TNumber;
// }>, TObject<{
// z: TLiteral<1>
// }>]>1.0.0
const X = Type.Object({ x: Type.Number(), z: Type.Number() })
const Y = Type.Object({ y: Type.Number(), z: Type.Literal(1) })
const Z = Type.Intersect([X, Y])
const T = Type.Pick(Z, ['z']) // const T: Type.TObject<{
// z: Type.TLiteral<1>;
// }>Version 1.0 makes API changes to the TypeBox compiler but retains the same functionality. The biggest change in 1.0 is the renaming of TypeCheck to Validator. Implementations that reference TypeCheck will need to updated reference Validator. Additionally, the TypeCompiler namespace has been deprecated, implementations can now use the Compile function exported on the submodule.
0.34.x
import { TypeCompiler, TypeCheck } from '@sinclair/typebox/compiler'
const C = TypeCompiler.Compile(Type.String()) // TypeCheck<TString>
C.Check('hello') // true1.0.0
import { Compile, Validator } from 'typebox/compile'
const C = Compile(Type.String()) // Validator<{}, TString>
C.Check('hello') // trueVersion 1.0 introduces a model for handling referential types. This model is reflected across Value and Compiler submodules. In 1.0, references are new passed via a optional Context object. This replaces the references array in 0.34.x.
0.34.x
const A = Type.String({ $id: 'A' })
const B = Type.Number({ $id: 'B' })
const T = Type.Object({
a: Type.Unsafe<Static<typeof A>>(Type.Ref('A')), // Unsafe Static for Inference
b: Type.Unsafe<Static<typeof B>>(Type.Ref('B')),
})
const C = TypeCompiler.Compile(T, [A, B]) // References Array
const R = C.Decode({ a: '', b: 1 }) // const R: { a: string, b: number }1.0.0
const A = Type.String()
const B = Type.Number()
const T = Type.Object({
a: Type.Ref('A'),
b: Type.Ref('B')
})
const C = Compile({ A, B }, T) // const R: { a: string, b: number }TypeBox 1.0 moves the FormatRegistry to the Format submodule.
0.34.x
import { FormatRegistry } from '@sinclair/typebox'
FormatRegistry.Set('foo', value => value === 'foo')1.0.0
import Format from 'typebox/format'
Format.Set('foo', value => value === 'foo')TypeBox 1.0 removes the TypeRegistry. This registry has been replaced by Type.Base that enables custom types to be created without the need for registration. The Base type provides the same capabilities as the TypeRegistry but is structured quite differently. The example comments show the changes.
0.34.x
import { TypeRegistry, Kind, TSchema } from '@sinclair/typebox'
// Definition
interface TFoo extends TSchema {
[Kind]: 'Foo'
static: 'Foo' // Static
}
// Check
TypeRegistry.Set('Foo', (value) => value === 'Foo')
// Factory
function Foo(): TFoo {
return { [Kind]: 'Foo' } as never
}1.0.0
import Type from '@sinclair/typebox'
// Definition
export class TFoo extends Type.Base<'Foo'> { // Static
// Check
public override Check(value: unknown): value is 'Foo' {
return value === 'Foo'
}
}
// Factory
export function Foo(): TDate {
return new TDate()
}TypeBox 1.0 removes the TypeGuard. The Is functions have been moved to Type.*.
0.34.x
import { Type, TypeGuard } from '@sinclair/typebox'
const T = Type.String()
const R = TypeGuard.IsString(T) // const R = true1.0.0
import Type from 'typebox'
const T = Type.String()
const R = Type.IsString(T) // const R = trueVersion 1.0 changes the Errors() return type from Iterator to Array. It also updates the Error to be inline with Ajv. This change enables TypeBox errors to integrate with existing systems that report errors using the Ajv structure. Existing applications will need to update error handling inline with the new structure.
0.34.x
export interface ValueError {
type: ValueErrorType
schema: TSchema
path: string
value: unknown
message: string
errors: ValueErrorIterator[]
}1.0.0
interface TLocalizedValidationError {
keyword: string
schemaPath: string
instancePath: string
params: object
message: string
}0.34.x
import { Value } from '@sinclair/typebox/value'
const T = Type.Number()
const E = [...Value.Errors(T, 'not a number')] // IterableIterator<Error>1.0.0
import Value from 'typebox/value'
const T = Type.Number()
const E = Value.Errors(T, 'not a number') // TLocalizedValidationError[]Version 1.0 renames Cast to Repair.
0.34.x
const T = Type.Object({ x: Type.Number() })
const R = Value.Cast(T, { }) // const R = { x: 0 }1.0.0
const T = Type.Object({ x: Type.Number() })
const R = Value.Repair(T, { }) // const R = { x: 0 }TypeBox 1.0 removes the SetErrorFunction callback. This callback was original written to provide internationalization (i18n) hooks for error messages. As Version 1.0 adds built-in support for error message localization, the SetErrorFunction as been removed. Error localization has been moved to the system module.
0.34.x
import { SetErrorFunction, DefaultErrorFunction, ValueErrorType } from '@sinclair/typebox/errors'
SetErrorFunction((error) => { // i18n override
switch(error.errorType) {
/* en-US */ case ValueErrorType.String: return 'Expected string'
/* fr-FR */ case ValueErrorType.Number: return 'Nombre attendu'
/* ko-KR */ case ValueErrorType.Boolean: return '예상 부울'
/* en-US */ default: return DefaultErrorFunction(error)
}
})1.0.0
import { Locale } from 'typebox/system'
Locale.Set(Locale.en_US) // en-US
Locale.Set(Locale.fr_FR) // fr-FR
Locale.Set(Locale.ko_KR) // ko-KR
Locale.Reset() // en-USA common pattern for handling custom errors in 0.34.x was to use the SetErrorFunction to intercept the errorMessage property and remap errors when it was present. Since SetErrorFunction has been removed in 1.0, implementations will need to adopt a new strategy for overriding error messages. In 1.0, errors are structured inline with Ajv errors to promote better interoperability with reporting tools that understand Ajv structures, and the mechanisms for handling sub-schema dereferencing have changed. Refer to the provided custom-errors.ts fallback as a reference for handling custom error messages in 1.0.
0.34.x
SetErrorFunction((parameter) =>
"errorMessage" in parameter.schema
? (parameter.schema.errorMessage as string)
: DefaultErrorFunction(parameter),
)1.0.0
import { CustomErrors } from './legacy/custom-errors' // refer to fallback implementation
import Type from 'typebox'
const T = Type.Object({
x: Type.Number({ errorMessage: 'Expected X Component' }),
y: Type.Number({ errorMessage: 'Expected Y Component' })
})
const E = CustomErrors(T, { x: '0', y: '0' }) // const E = [{
// keyword: 'type',
// schemaPath: '#/properties/x',
// instancePath: '/x',
// params: { type: "number" },
// message: "Expected X Component"
// }, {
// keyword: "type",
// schemaPath: '#/properties/y',
// instancePath: '/y',
// params: { type: "number" },
// message: 'Expected Y Component'
// }]