Skip to content
Devix Open Source

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