How to Validate Canadian Phone Numbers in Code

Phone validation is one of those tasks that looks simple and then causes bugs for years. This guide shows three levels of validation, from a quick regex to a library-backed check, so you can pick the right one for your project.

Level 1: Basic format check with regex

A simple regular expression can verify that a number is in valid NANP shape once you have stripped formatting characters.

// Accepts +1 or 1 prefix, optional separators, valid NANP structure
const NANP = /^(?:\+?1[\s.-]?)?\(?([2-9]\d{2})\)?[\s.-]?([2-9]\d{2})[\s.-]?(\d{4})$/;
function isNanpFormat(input) {
  return NANP.test(input.trim());
}
isNanpFormat("+1 (416) 555-0123"); // true
isNanpFormat("416-555-0123"); // true
isNanpFormat("123-555-0123"); // false (area code starts with 1)

This tells you the number is shaped correctly. It does not tell you the area code is Canadian or that the number exists.

Level 2: Check the area code is Canadian

Since Canada and the US share +1, you need a list of Canadian area codes to separate them.

const CANADIAN_AREA_CODES = new Set([
  "204","226","236","249","250","263","289","306","343","354","365","367",
  "368","382","403","416","418","428","431","437","438","450","468","474",
  "506","514","519","548","579","581","584","587","604","613","639","647",
  "672","683","705","709","742","778","780","782","807","819","825","867",
  "873","879","902","905"
]);
function isCanadianNumber(input) {
  const digits = input.replace(/\D/g, "").replace(/^1/, "");
  if (digits.length !== 10) return false;
  return CANADIAN_AREA_CODES.has(digits.slice(0, 3));
}

The same idea in Python:

import re
CANADIAN_AREA_CODES = {
    "204","226","236","249","250","289","306","343","365","367","368","403",
    "416","418","428","431","437","438","450","474","506","514","519","548",
    "579","581","584","587","604","613","639","647","672","705","709","778",
    "780","782","807","819","825","867","873","879","902","905",
}
def is_canadian_number(raw: str) -> bool:
    digits = re.sub(r"\D", "", raw)
    if len(digits) == 11 and digits.startswith("1"):
        digits = digits[1:]
    return len(digits) == 10 and digits[:3] in CANADIAN_AREA_CODES

Area codes are added from time to time, so keep your list in a data file and check it against the official Canadian Numbering Administrator (cnac.ca) periodically. Treat the lists above as a starting point and verify them before relying on them in production.

Level 3: Use libphonenumber

For production systems, use Google's libphonenumber or a port of it. It knows the numbering rules for every country, handles formatting and can tell you whether a number is plausible and valid.

import phonenumbers
num = phonenumbers.parse("+1 416 555 0123", None)
print(phonenumbers.is_possible_number(num))
print(phonenumbers.is_valid_number(num))
print(phonenumbers.region_code_for_number(num)) # "CA" for Canadian numbers
print(phonenumbers.format_number(num, phonenumbers.PhoneNumberFormat.E164))
import { parsePhoneNumberFromString } from "libphonenumber-js";
const phone = parsePhoneNumberFromString("+1 416 555 0123");
console.log(phone?.isValid(), phone?.country, phone?.format("E.164"));

Note that fictional 555-01XX numbers are often rejected by libphonenumber's strict validity check, because they are reserved and not assigned. If your tests use them, test with is_possible_number or use a mock.

Format versus reachability

Even a number that passes every check may not be in service. Format validation tells you the string is well formed. To confirm a user really owns the number, send a verification code by SMS or a call. Never assume that a valid-looking number belongs to the person entering it.

Validation best practices

Test data

You will need sample numbers to test all this. Use the generator for random numbers across Canadian area codes, and read our guide to fake numbers for testing for the safest options.