Recursive AI

Validate a phone number in C#

OneContact is a free, open-source contact data library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies, no network access.

Check a phone number against its region’s numbering rules before accepting it in a form.

Terminal
dotnet add package RecursiveAI.OneContact

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

C#
using RecursiveAI.OneContact;
 
bool valid = PhoneNumber.TryParse(
    "(202) 555-0123", Region.US, out var phone)
    && phone.IsValid;
Console.WriteLine(valid);
Text
True

Download this example (.zip): unzip, then dotnet run.

Full program

C#
using RecursiveAI.OneContact;
 
foreach (string input in new[] { "(202) 555-0123", "123", "not a phone" })
{
    if (!PhoneNumber.TryParse(input, Region.US, out var phone))
    {
        Console.WriteLine($"{input}: cannot parse");
        continue;
    }
    Console.WriteLine($"{input}: valid={phone.IsValid}, possibility={phone.GetPossibility()}");
}

dotnet run prints:

Text
(202) 555-0123: valid=True, possibility=IsPossible
123: valid=False, possibility=TooShort
not a phone: cannot parse

The full program distinguishes an invalid number from text that cannot be parsed. TryParse returns false for rejected input; Parse instead throws ContactParseException.

Variations

Check validity for a particular region and read the number type:

C#
using RecursiveAI.OneContact;
 
var phone = PhoneNumber.Parse("+44 20 7946 0123");
Console.WriteLine(phone.IsValidForRegion(Region.Parse("GB")));
Console.WriteLine(phone.Type);
Text
True
FixedLine

The short answer

Use PhoneNumber.TryParse with a default region, then check IsValid. Those are separate tests: a string can parse into a phone value without matching a valid number type in its region. The full program makes that difference visible. The string 123 parses, but it is too short and isn’t valid. Unreadable text fails parsing altogether.

“Valid” here means valid against the numbering metadata, not that a subscriber answers the phone. The library does not contact the number or a carrier. Use this check to catch malformed input, not to establish account ownership. If ownership matters to your application, make confirmation a separate step.

Is there a way to validate a phone number?

There are three useful questions to ask in order:

  • Can the input be parsed? TryParse answers this without throwing for rejected input.
  • Is its length possible? GetPossibility() distinguishes cases such as too short, too long and possible.
  • Does it match a recognized number type? IsValid answers this using the region’s numbering rules.

Use the last test when accepting a complete number. Use the possibility result when explaining an incomplete number to someone still typing. A possible length alone is not the same as a valid number.

The region is not a formatting preference. A national number needs the country context in which it was entered. Region.US supplies that context for the example; Region.Parse("GB") supplies it for Britain. A plus-prefixed international number can be parsed without a default region. Without a plus sign or a default region, parsing fails with phone.region-required.

IsValidForRegion asks a narrower question than IsValid: does the number belong to the specified region’s plan? The variation demonstrates this for a British fixed line. Don’t impose that narrower test unless your form actually requires a number from that region. An international customer can legitimately supply a foreign number.

Can you provide a regex to validate phone numbers?

No single regex over the input covers real numbering plans. Use OneContact’s parser to apply the region’s numbering rules instead. It accepts punctuation, national and international prefixes, extensions and vanity input under the documented parsing rules. A digit-count check would miss the distinction between a plausible length and a recognized number type.

Keep presentation separate from validation. Preserve what the user typed if you need it for editing, validate the parsed value, and use E164 for an unpunctuated international representation. If the input has an extension, keep that separately: E.164 output omits it. The canonical form includes it as a ;ext= parameter.

The numbering data is bundled with this package release. It is not a live carrier directory, and changing your application’s acceptance policy is separate from updating that data. The phone documentation describes metadata loading, reason codes and supported operations. For example, a rejected national input may need a region rather than different punctuation. Showing that specific problem is more useful than telling everyone to remove every non-digit character.

As the OneContact release overview states: “Version 1.0 runs on rules and markup alone.”

All onecontact recipes