Guide
Recipes
A field that validates as you type
import { mask, validate } from '@devix-labs/validators';
const field = document.querySelector('#cnic');
field.placeholder = mask('cnic').replaceAll('9', '0');
field.addEventListener('input', () => {
const result = validate('cnic', field.value);
// Don't shout while someone is still typing: only 'too-long' and 'bad-structure' are final.
const settled = field.value.replace(/\D/g, '').length >= 13;
field.setCustomValidity(settled && !result.valid ? message(result.reason) : '');
});
field.addEventListener('blur', () => {
const result = validate('cnic', field.value);
if (result.valid) field.value = result.formatted;
});
Accepting an IBAN from only your own markets
const result = validate('iban', value, { only: ['AE', 'SA', 'PK'] });
// reason: 'unknown-country' for anything else
To accept a country the registry does not cover, on its check digits alone:
validate('iban', value, { allowUnknown: true });
A VAT field where the country is already known
validate('vat', '136695976', { country: 'DE' }); // the prefix is optional
validate('vat', 'DE136695976'); // or carried by the number
Checking a business customer at sign-up
const trn = validate('uae-trn', form.trn.value);
if (!trn.valid) return show(message(trn.reason));
if (trn.checked === 'structure') {
// It looks right, but nothing here proves it is registered. Queue a check against
// the FTA's own service, and let the sign-up continue.
await queueTrnVerification(trn.value);
}
React
import { useMemo } from 'react';
import { validate } from '@devix-labs/validators';
function IbanField({ value, onChange }) {
const result = useMemo(() => validate('iban', value, { only: ['AE', 'SA'] }), [value]);
return (
<label>
IBAN
<input value={value} onChange={(event) => onChange(event.target.value)} onBlur={() => result.valid && onChange(result.formatted)} />
{value && !result.valid && <small>{MESSAGES[result.reason]}</small>}
{result.valid && <small>{result.details.bankCode}</small>}
</label>
);
}
Seeding a database with valid data
import { generate } from '@devix-labs/validators';
const customers = Array.from({ length: 500 }, (_, index) => ({
name: `Customer ${index}`,
emiratesId: generate('emirates-id', { seed: index }),
iban: generate('iban', { country: 'AE', seed: index }),
}));
A seed gives the same number every time, so a fixture stays stable between runs.
Your own numbers, with our primitives
import { luhn, mod97, weightedMod11, luhn36 } from '@devix-labs/validators';
const membershipNumber = (value) => /^M\d{9}$/.test(value) && luhn(value.slice(1));
Validating a spreadsheet
import { kinds, validate } from '@devix-labs/validators';
const problems = rows.flatMap((row, line) =>
Object.entries(row)
.filter(([column]) => kinds().includes(column))
.map(([column, value]) => ({ line, column, ...validate(column, value) }))
.filter((result) => !result.valid),
);
Updated 15 Sep 2026