Skip to content

Masks & validation ​

A mask is about how a value looks, validation is about whether it's correct. A mask won't let a letter into a phone number, but it doesn't know the number is unfinished or that a tax ID checksum is wrong.

Check that the mask is filled ​

isComplete — all required positions are filled. isValid — filled and correct (what exactly is checked — in Ready-made masks):

ts
isComplete('+7 (999) 123-45', phone());    // false
isComplete('+7 (999) 123-45-67', phone()); // true

// filled and correct
isValid('31.02.2026', date());              // false — no such date
isValid('4111 1111 1111 1112', card());     // false — wrong check digit

With a tinyval schema ​

Remove the mask in preprocess — then the clean value is validated and goes to data:

ts
import { validate, object, string, pipe, preprocess, length, pattern, coerce } from 'tinyval';
import { unmask, isComplete } from 'tinyval-mask';
import { phone, card, money } from 'tinyval-mask/presets';

const phoneMask = phone();
const cardMask = card();
const moneyMask = money();

// the mask is removed first, then the clean value is validated
const unmasked = (m: Parameters<typeof unmask>[1]) => (v: unknown) => (v === '' || v == null ? v : unmask(String(v), m));

const Order = object({
  phone: preprocess(unmasked(phoneMask), pipe(string(), length(11))),
  card: preprocess(unmasked(cardMask), pipe(string(), pattern(/^\d{16,19}$/))),
  amount: preprocess(unmasked(moneyMask), coerce.number()),
});

validate(Order, { phone: '+7 (999) 123-45-67', card: '4276 1234 5678 9012', amount: '1 500,50' }).data;
// { phone: '79991234567', card: '4276123456789012', amount: 1500.5 }
  • An empty value stays empty — the empty values rule and optional work.
  • The same works on the server: masks are pure functions, so unmask normalizes phones sent in any format.

See also ​