Validation
The validate() function — how it works, what it returns, and how to use it for creates and updates.
validate() is the core function that checks a value against a schema. It returns a discriminated union: either a success with the validated values, or a failure with structured errors.
Signature
function validate<T extends Schema>(
schema: T,
currentValues: SchemaValues<T> | undefined,
newValues: unknown,
options?: ValidateOptions,
): Promise<ValidationResult<SchemaValues<T>>>validate() is async — always await it.
Parameters
schema
Any dynz schema: d.object(...), d.string(), d.array(...), etc. For form validation the root schema is typically an object.
currentValues
The existing value of the record being validated.
- Pass
undefinedfor creates (new records). Mutability enforcement is disabled. - Pass the existing record for updates. Fields with
setMutable(false)or afalse-evaluating mutable predicate will fail if their value changed.
// Create — no mutability check
await d.validate(schema, undefined, newData);
// Update — mutability is enforced
await d.validate(schema, existingRecord, updatedData);newValues
The incoming data to validate. Typed as unknown — dynz validates the type as part of the process.
options
type ValidateOptions = {
customRules?: Record<string, CustomRuleFunction>;
stripNotIncludedValues?: boolean;
};See Custom rules for customRules usage.
stripNotIncludedValues: true removes values for excluded fields from the output instead of treating them as errors. Useful when a form might submit stale values for fields that have become conditionally excluded.
Return value
validate() returns a ValidationResult<T>:
type ValidationResult<T> =
| { success: true; values: T }
| { success: false; errors: ErrorMessage[] };Success
const result = await d.validate(schema, undefined, { name: 'Alice', age: 30 });
if (result.success) {
result.values // { name: 'Alice', age: 30 } — fully typed as SchemaValues<typeof schema>
}Failure
if (!result.success) {
result.errors // ErrorMessage[]
}Each ErrorMessage has:
| Field | Type | Description |
|---|---|---|
path | string | Dot-path to the failing field, e.g. $.address.zip |
code | string | Built-in or custom error code |
customCode | string | The custom code from .custom() or a rule's code argument |
message | string | Human-readable description |
value | unknown | The submitted value that failed |
current | unknown | The current (pre-update) value, if any |
schema | Schema | The schema that produced the error |
Error codes
| Code | Condition |
|---|---|
required | Required field is missing |
type | Value is the wrong type |
immutable | Mutable field changed on update |
included | Value submitted for an excluded field |
min_length | String too short |
max_length | String too long |
email | Invalid email |
regex | Doesn't match pattern |
equals | Value doesn't equal expected |
one_of | Value not in allowed set |
is_numeric | Not a numeric string |
min | Number below minimum |
max | Number above maximum |
max_precision | Too many decimal places |
after | Date not after threshold |
before | Date not before threshold |
min_date | Date before minimum |
max_date | Date after maximum |
min_entries | Array too short |
max_entries | Array too long |
min_size | File too small |
max_size | File too large |
mime_type | File MIME type not allowed |
Validation order
For each field, validate() runs checks in this order and stops at the first failure:
- Included — if the field is excluded, stop (success if no value, error if value present)
- Required — if required and no value, return required error
- Optional early exit — if not required and no value, return success
- Type check — verify the value's JS type matches the schema type
- Rules — evaluate each rule in order, stop at the first failure
- Nested fields — for objects and arrays, recurse into children
All nested validations for objects and arrays run in parallel, and all errors are collected before returning. A single validate() call returns every error across the full object tree.
Examples
Simple validation
const schema = d.object({
username: d.string().min(3).max(20),
email: d.string().email(),
age: d.number().min(18),
});
const result = await d.validate(schema, undefined, {
username: 'al',
email: 'not-an-email',
age: 15,
});
// result.success === false
// result.errors === [
// { path: '$.username', code: 'min_length', ... },
// { path: '$.email', code: 'email', ... },
// { path: '$.age', code: 'min', ... },
// ]Update with mutability
const orderSchema = d.object({
status: d.options(['draft', 'submitted']).setMutable(false),
notes: d.string().optional(),
});
const existingOrder = { status: 'submitted', notes: 'Urgent' };
const result = await d.validate(orderSchema, existingOrder, {
status: 'draft', // ← changed — but status is immutable!
notes: 'Not urgent',
});
// result.success === false
// result.errors === [{ path: '$.status', code: 'immutable', ... }]Stripping excluded values
const schema = d.object({
plan: d.options(['free', 'pro']),
proFeature: d.string().setIncluded(d.eq(d.ref('plan'), 'pro')),
});
// Without stripNotIncludedValues — error because proFeature is present
await d.validate(schema, undefined, { plan: 'free', proFeature: 'something' });
// → { success: false, errors: [{ path: '$.proFeature', code: 'included' }] }
// With stripNotIncludedValues — proFeature is silently removed
await d.validate(schema, undefined, { plan: 'free', proFeature: 'something' }, {
stripNotIncludedValues: true,
});
// → { success: true, values: { plan: 'free', proFeature: undefined } }