Dynz.

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 undefined for creates (new records). Mutability enforcement is disabled.
  • Pass the existing record for updates. Fields with setMutable(false) or a false-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:

FieldTypeDescription
pathstringDot-path to the failing field, e.g. $.address.zip
codestringBuilt-in or custom error code
customCodestringThe custom code from .custom() or a rule's code argument
messagestringHuman-readable description
valueunknownThe submitted value that failed
currentunknownThe current (pre-update) value, if any
schemaSchemaThe schema that produced the error

Error codes

CodeCondition
requiredRequired field is missing
typeValue is the wrong type
immutableMutable field changed on update
includedValue submitted for an excluded field
min_lengthString too short
max_lengthString too long
emailInvalid email
regexDoesn't match pattern
equalsValue doesn't equal expected
one_ofValue not in allowed set
is_numericNot a numeric string
minNumber below minimum
maxNumber above maximum
max_precisionToo many decimal places
afterDate not after threshold
beforeDate not before threshold
min_dateDate before minimum
max_dateDate after maximum
min_entriesArray too short
max_entriesArray too long
min_sizeFile too small
max_sizeFile too large
mime_typeFile MIME type not allowed

Validation order

For each field, validate() runs checks in this order and stops at the first failure:

  1. Included — if the field is excluded, stop (success if no value, error if value present)
  2. Required — if required and no value, return required error
  3. Optional early exit — if not required and no value, return success
  4. Type check — verify the value's JS type matches the schema type
  5. Rules — evaluate each rule in order, stop at the first failure
  6. 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 } }

On this page