Phone Validation Plugin
@kanun-hq/plugin-phone adds a phone rule, a fluent Phone rule builder, parsed PhoneNumber validated output, formatting helpers, and Arkormx-style casts.
The plugin uses libphonenumber-js/max for parsing, validation, country detection, formatting, and number type metadata.
Installation
npm install kanun @kanun-hq/plugin-phonepnpm add kanun @kanun-hq/plugin-phoneyarn add kanun @kanun-hq/plugin-phoneRegister the plugin once during application startup:
import { Validator } from 'kanun';
import { phoneValidatorPlugin } from '@kanun-hq/plugin-phone';
Validator.use(phoneValidatorPlugin);Basic Validation
Use the phone rule without parameters when the input includes enough country information, such as an international number.
const validator = Validator.make(
{ my_input: '+32 12 34 56 78' },
{ my_input: 'phone' },
);When the input is national, pass one or more country codes.
const validated = await Validator.make(
{ my_input: '012 34 56 78' },
{ my_input: 'phone:BE' },
).validate();
validated.my_input.formatE164(); // '+3212345678'The same rule can be written with the fluent builder:
import { Phone } from '@kanun-hq/plugin-phone';
const validator = Validator.make(
{ my_input: '012 34 56 78' },
{ my_input: Phone.country(['US', 'BE']) },
);Validated Output
When the phone rule passes, validate() returns a parsed PhoneNumber instance for that field.
const validated = await Validator.make(
{ my_input: '012 34 56 78' },
{ my_input: 'phone:BE' },
).validate();
validated.my_input.formatE164(); // '+3212345678'
validated.my_input.getCountry(); // 'BE'
validated.my_input.getType(); // 'fixed_line'Country Field Validation
Use phone:field_name when another input field stores the country code.
const validated = await Validator.make(
{
my_input: '012 34 56 78',
custom_country_field: 'BE',
},
{
my_input: 'phone:custom_country_field',
custom_country_field: 'required_with:my_input',
},
).validate();
validated.my_input.formatE164(); // '+3212345678'Builder form:
const validator = Validator.make(
{
my_input: '012 34 56 78',
custom_country_field: 'BE',
},
{
my_input: Phone.countryField('custom_country_field'),
custom_country_field: 'required_with:my_input',
},
);International Numbers
Use INTERNATIONAL to allow country detection from the phone number itself while still applying any country constraints that follow.
const validator = Validator.make(
{ my_input: '+32 12 34 56 78' },
{ my_input: 'phone:INTERNATIONAL,BE' },
);Builder form:
const validator = Validator.make(
{ my_input: '+32 12 34 56 78' },
{ my_input: Phone.international().country('BE') },
);Phone Number Types
Use type parameters to require a specific number type.
const validator = Validator.make(
{ my_input: '+32 470 12 34 56' },
{ my_input: 'phone:mobile' },
);Builder form:
const validator = Validator.make(
{ my_input: '+32 470 12 34 56' },
{ my_input: Phone.type('mobile') },
);Use ! to reject a type.
const validator = Validator.make(
{ my_input: '+32 2 555 12 12' },
{ my_input: 'phone:!mobile' },
);Builder form:
const validator = Validator.make(
{ my_input: '+32 2 555 12 12' },
{ my_input: Phone.notType('mobile') },
);Supported type names:
mobilefixed_linefixed_line_or_mobiletoll_freevoippremium_rateshared_costpersonal_numberpageruanunknownemergencyvoicemailshort_codestandard_rate
Some type names depend on what libphonenumber-js metadata can identify. Unsupported metadata-specific types simply do not match PhoneNumber.isOfType().
Lenient Validation
Use LENIENT when you want possible phone numbers to pass even if they are not strictly valid according to numbering-plan metadata.
const validator = Validator.make(
{ my_input: '+1 200 123 0101' },
{ my_input: 'phone:LENIENT' },
);Builder form:
const validator = Validator.make(
{ my_input: '+1 200 123 0101' },
{ my_input: Phone.lenient() },
);PhoneNumber Utility
Use PhoneNumber directly when you want parsing, normalization, formatting, and comparisons outside a validator.
import { PhoneNumber } from '@kanun-hq/plugin-phone';
const phoneNumber = new PhoneNumber('+3212/34.56.78');
phoneNumber.format(); // '+3212345678'
phoneNumber.formatE164(); // '+3212345678'
phoneNumber.formatInternational(); // '+32 12 34 56 78'
phoneNumber.formatRFC3966(); // 'tel:+3212345678'
phoneNumber.formatNational(); // '012 34 56 78'
phoneNumber.formatNationalSignificant(); // '12 34 56 78'
phoneNumber.formatNationalSignificant(true); // '12345678'
phoneNumber.formatForCountry('BE'); // '012 34 56 78'
phoneNumber.formatForMobileDialingInCountry('US'); // '011 32 12 34 56 78'
phoneNumber.getType(); // 'fixed_line'
phoneNumber.isOfType('fixed_line'); // true
phoneNumber.getCountry(); // 'BE'
phoneNumber.isOfCountry('BE'); // true
phoneNumber.equals(new PhoneNumber('012 34 56 78', 'BE')); // true
phoneNumber.notEquals('+32 470 12 34 56'); // trueformatNationalSignificant() is useful when you want to display the subscriber-facing national number without the country calling code or leading national prefix:
new PhoneNumber('+234 903 123 4567').formatNationalSignificant(); // '903 123 4567'
new PhoneNumber('+234 903 123 4567').formatNationalSignificant(true); // '9031234567'
new PhoneNumber('+32 12 34 56 78').formatNationalSignificant(); // '12 34 56 78'National numbers can be parsed with a country:
const phoneNumber = new PhoneNumber('012 34 56 78', 'BE');
phoneNumber.formatE164(); // '+3212345678'Use the nullable parser when invalid input should not throw:
PhoneNumber.parse('not a phone number'); // nullPhone Helper
The phone helper is a callable shortcut around PhoneNumber.parse() with common formatting helpers attached.
import { phone } from '@kanun-hq/plugin-phone';
const parsed = phone('012 34 56 78', 'BE');
parsed?.formatE164(); // '+3212345678'
phone.format('+3212/34.56.78'); // '+3212345678'
phone.formatE164('012 34 56 78', 'BE'); // '+3212345678'
phone.formatInternational('012 34 56 78', 'BE'); // '+32 12 34 56 78'
phone.formatNational('+3212345678'); // '012 34 56 78'
phone.formatNationalSignificant('+234 903 123 4567'); // '903 123 4567'
phone.formatNationalSignificant('+234 903 123 4567', undefined, true); // '9031234567'Arkormx Casts
Use RawPhoneNumberCast or E164PhoneNumberCast for model cast definitions.
import { Model } from 'arkormx';
import { RawPhoneNumberCast } from '@kanun-hq/plugin-phone';
export class User extends Model {
protected override casts = {
phone: new RawPhoneNumberCast('country_field'),
} as const;
}You can pass a fixed country instead of a field name.
export class User extends Model {
protected override casts = {
phone: new RawPhoneNumberCast('BE'),
} as const;
}E164PhoneNumberCast always stores E.164 strings.
import { E164PhoneNumberCast } from '@kanun-hq/plugin-phone';
export class User extends Model {
protected override casts = {
phone: new E164PhoneNumberCast('BE'),
} as const;
}