Validation

Validation in tldraw is handled by @tldraw/validate and applied across schemas, record types, and shape props. Validators enforce runtime type safety and provide structured errors when data is malformed.

Where validation runs

  • Shape and binding props via RecordProps
  • Record types in the Store via createRecordType and StoreSchema
  • Worker request parsing via parseRequestBody and parseRequestQuery

Core validators

Use T validators to describe data shapes and validate unknown input:

import { T } from '@tldraw/validate'

const userValidator = T.object({
	id: T.string,
	name: T.string.optional(),
	age: T.number.optional(),
})

const user = userValidator.validate(input)

Every validator has three key methods:

  • validate(value) - validates unknown input and returns a typed result
  • isValid(value) - returns true if valid, false otherwise (useful as a type guard)
  • validateUsingKnownGoodVersion(knownGood, newValue) - performance-optimized validation that reuses previously validated data

Validator catalog

Primitives

  • T.unknown, T.any
  • T.string, T.number, T.boolean, T.bigint

Numbers

  • T.positiveNumber, T.nonZeroNumber, T.nonZeroFiniteNumber
  • T.unitInterval, T.integer, T.positiveInteger, T.nonZeroInteger

Collections and objects

  • T.array, T.arrayOf, T.object, T.unknownObject
  • T.dict, T.jsonDict, T.jsonValue

Unions and enums

  • T.literal, T.literalEnum, T.setEnum
  • T.union, T.or

URLs and identifiers

  • T.linkUrl, T.srcUrl, T.httpUrl, T.indexKey

Modifiers and helpers

  • validator.optional(), validator.nullable()
  • T.optional(...), T.nullable(...)
  • validator.refine(...), validator.check(...)
  • T.model(...)

Common validator patterns

import { T, ValidationError } from '@tldraw/validate'

const configValidator = T.object({
	id: T.string,
	mode: T.literalEnum('view', 'edit'),
	tags: T.arrayOf(T.string).optional(),
	meta: T.object({ note: T.string }).nullable(),
})

const evenNumber = T.number.check('even', (value) => {
	if (value % 2 !== 0) throw new ValidationError('Expected even number')
})

Record props validation

Shapes and bindings use RecordProps to validate their props at runtime. This keeps stored data consistent with the schema.

import { ShapeUtil, type RecordProps, T, DefaultColorStyle } from 'tldraw'

class MyShapeUtil extends ShapeUtil<MyShape> {
	static override props: RecordProps<MyShape> = {
		color: DefaultColorStyle,
		text: T.string,
	}
}

Store validation and recovery

The Store validates records on write. You can provide onValidationFailure to recover or sanitize data:

import { StoreSchema, createRecordType } from '@tldraw/store'

const Book = createRecordType<Book>('book', { scope: 'document' })

const schema = StoreSchema.create(
	{ book: Book },
	{
		onValidationFailure: (failure) => failure.record,
	}
)

The failure object contains:

PropertyDescription
errorThe validation error that occurred
storeThe store instance where validation failed
recordThe invalid record
phaseWhen validation failed: 'initialize', 'createRecord', 'updateRecord', or 'tests'
recordBeforeThe previous record state (null for new records)

Validation lifecycle

  1. A record is created or updated.
  2. The record type validator runs.
  3. If validation fails, onValidationFailure receives the failure object.
  4. The handler can return a corrected record or throw to abort the write.

Error handling

The ValidationError class provides structured information about what went wrong:

import { T, ValidationError } from '@tldraw/validate'

const userValidator = T.object({
	name: T.string,
	settings: T.object({ theme: T.literalEnum('light', 'dark') }),
})

try {
	userValidator.validate({ name: 'Alice', settings: { theme: 'invalid' } })
} catch (error) {
	if (error instanceof ValidationError) {
		console.log(error.message) // "At settings.theme: Expected "light" or "dark", got "invalid""
		console.log(error.rawMessage) // 'Expected "light" or "dark", got "invalid"'
		console.log(error.path) // ['settings', 'theme']
	}
}

The error has two useful properties:

  • rawMessage - the error message without path information
  • path - an array showing where in the data structure validation failed (e.g., ['settings', 'theme'] or ['items', 0, 'name'])

The full message property combines these: "At settings.theme: Expected...".

Gotchas

  • Validators must be pure and must not mutate input values.
  • If you use onValidationFailure, return a valid record or rethrow to abort the write.
Prev
User preferences
Next
Visibility