Types Validation ​
Overview ​
Owl provides a type validation system built around the types object, which contains composable validators for common JavaScript types.
The main use case is props validation: when developing components in dev mode, Owl uses these validators to check that props received by a component match their declared types. This catches bugs early by reporting mismatches at render time rather than letting them propagate silently.
import { Component, xml, types as t, props } from "@odoo/owl";
class UserCard extends Component {
static template = xml`
<div>
<span t-out="this.props.name"/>
<span t-if="this.props.age" t-out="this.props.age"/>
</div>`;
props = props({
name: t.string(),
"age?": t.number(),
});
}The validators can also be used standalone via validateType and assertType to validate any value at runtime, not just props.
validateType ​
Checks a value against a validator and returns a list of validation issues. An empty list means the value is valid.
import { types as t, validateType } from "@odoo/owl";
validateType(42, t.number()); // [] (valid)
validateType("hello", t.number()); // [{ message: "value is not a number" }]
validateType([1, 2, 3], t.array(t.number())); // [] (valid)
validateType([1, "two"], t.array(t.number())); // [{ message: "value is not a number" }]
const userType = t.object({
name: t.string(),
"age?": t.number(),
});
validateType({ name: "Alice" }, userType); // [] (valid)
validateType({ name: "Alice", age: 30 }, userType); // [] (valid)
validateType({ age: 30 }, userType); // [{ message: "object value has missing keys" }]assertType ​
Like validateType, but throws an OwlError if validation fails instead of returning the issues list. Accepts an optional error message prefix.
import { types as t, assertType } from "@odoo/owl";
assertType(42, t.number()); // ok, does nothing
assertType("hello", t.number());
// throws: "Value does not match the type\n[...]"
assertType("hello", t.number(), "Invalid config");
// throws: "Invalid config\n[...]"Validators ​
Owl exports a types object (aliased as t below) containing the following validators. Each validator can be used with validateType, assertType, or as a prop type in a component's props definition.
import { types as t } from "@odoo/owl";t.any() ​
Accepts any value without validation.
t.any();
// validates: 42, "hello", null, undefined, ...t.boolean() ​
Validates that the value is a boolean.
t.boolean();
// validates: true, false
// rejects: 0, "true", nullt.number() ​
Validates that the value is a number.
t.number();
// validates: 42, 3.14, NaN
// rejects: "42", nullt.string() ​
Validates that the value is a string.
t.string();
// validates: "hello", ""
// rejects: 42, nullt.array(elementType?) ​
Validates that the value is an array. When elementType is provided, each element is validated against it.
t.array(); // any array
t.array(t.number()); // array of numbers
t.array(t.string()); // array of strings
// validates: [1, 2, 3] with t.array(t.number())
// rejects: [1, "two", 3] with t.array(t.number())t.object(shape?) ​
Validates that the value is an object. When a shape is provided (either an object mapping keys to validators, or an array of key names), the object is checked for the expected keys. Keys ending with ? are optional. Extra keys are allowed.
t.object(); // any object
t.object(["name", "age"]); // must have "name" and "age" keys
t.object({ name: t.string(), "age?": t.number() }); // "name" required, "age" optional
// validates: { name: "Alice", age: 30, extra: true }
// rejects: { age: 30 } (missing required key "name")t.strictObject(shape) ​
Like t.object, but also rejects unknown keys not present in the schema. Accepts either an array of key names or an object mapping keys to validators.
t.strictObject(["name", "age"]); // must have exactly "name" and "age" keys
t.strictObject({ name: t.string(), "age?": t.number() }); // "name" required, "age" optional
// validates: { name: "Alice" }
// validates: { name: "Alice", age: 30 }
// rejects: { name: "Alice", extra: true } (unknown key "extra")t.record(valueType?) ​
Validates that the value is an object. When valueType is provided, every value in the object is validated against it.
t.record(); // any object
t.record(t.number()); // all values must be numbers
// validates: { a: 1, b: 2 } with t.record(t.number())
// rejects: { a: 1, b: "two" } with t.record(t.number())t.tuple(types) ​
Validates that the value is an array with a fixed length, where each element matches its corresponding type.
t.tuple([t.string(), t.number()]);
// validates: ["hello", 42]
// rejects: ["hello"] (wrong length)
// rejects: ["hello", "world"] (second element is not a number)t.function(params?, returnType?) ​
Validates that the value is a function. At runtime, only the typeof check is performed — the params and returnType arguments are not validated at runtime, they only exist to provide TypeScript type inference.
t.function(); // any function
t.function([t.string(), t.number()]); // typed params (TS inference only)
t.function([t.string()], t.boolean()); // typed params and return (TS inference only)
// validates: () => {}, Math.max, class Foo {}
// rejects: 42, "hello", nullt.promise(type?) ​
Validates that the value is a Promise. At runtime, only the instanceof check is performed — the type argument is not validated at runtime, it only exists to provide TypeScript type inference.
t.promise(); // any promise
t.promise(t.string()); // Promise<string> (TS inference only)
// validates: Promise.resolve(42), new Promise(() => {})
// rejects: 42, { then() {} }t.literal(value) ​
Validates that the value is strictly equal (===) to the given literal.
t.literal("admin");
t.literal(0);
t.literal(null);
// validates: "admin" with t.literal("admin")
// rejects: "user" with t.literal("admin")t.selection(values) ​
Validates that the value matches one of several literal values. This is shorthand for t.or with t.literal for each value.
t.selection(["small", "medium", "large"]);
// validates: "small", "medium", "large"
// rejects: "xl", 0, nullt.instanceOf(constructor) ​
Validates that the value is an instance of the given constructor.
t.instanceOf(Date);
t.instanceOf(HTMLInputElement);
// validates: new Date() with t.instanceOf(Date)
// rejects: Date.now() with t.instanceOf(Date)
// rejects: "2024-01-01" with t.instanceOf(Date)t.component() ​
Validates that the value is Component or a subclass of it. This is shorthand for t.constructor(Component).
t.component();
// validates: Component, MyComponent (extends Component)
// rejects: new Component(), "Component"t.constructor(constructor) ​
Validates that the value is the given constructor itself or a subclass of it.
t.constructor(Error);
// validates: Error, TypeError, RangeError
// rejects: new Error(), "Error"t.signal(type?) ​
Validates that the value is a reactive value (signal). The optional type argument is used for type inference only.
t.signal(); // any signal
t.signal(t.number()); // Signal<number> (for TS inference)t.ref(type?) ​
Validates that the value is either null or an instance of HTMLElement (or a subclass). Useful for component ref props.
t.ref(); // null | HTMLElement
t.ref(HTMLInputElement); // null | HTMLInputElementt.or(types) ​
Validates that the value matches at least one of the given types (union).
t.or([t.string(), t.number()]);
t.or([t.literal("none"), t.number()]);
// validates: "hello" with t.or([t.string(), t.number()])
// validates: 42 with t.or([t.string(), t.number()])
// rejects: true with t.or([t.string(), t.number()])t.and(types) ​
Validates that the value matches all of the given types (intersection).
t.and([t.object({ name: t.string() }), t.object({ age: t.number() })]);
// validates: { name: "Alice", age: 30 }
// rejects: { name: "Alice" } (missing "age")t.customValidator(type, predicate, errorMessage?) ​
Validates the value against a base type, then applies a custom predicate function. If the predicate returns false, validation fails with the given error message (defaults to "value does not match custom validation").
t.customValidator(t.number(), (v) => v >= 0, "value must be non-negative");
t.customValidator(t.string(), (v) => v.length > 0, "value must not be empty");
t.customValidator(t.array(t.number()), (v) => v.length <= 10, "too many items");
// validates: 42 with the first example
// rejects: -1 with the first example ("value must be non-negative")
// rejects: "hi" with the first example (fails base type: "value is not a number")