Regex for Numbers, Phone Numbers and IP Addresses

Copy-ready patterns for numeric input, phone numbers and IP addresses, each tested, with notes on where a parser or library does the job better.

Try it: Regex Tester

Numbers, phone numbers and IP addresses look like easy regex targets because they are mostly digits. Each has a trap: \d means different things in different languages, phone numbers follow national rules no pattern can capture, and an IP address is only valid if every part is in range. The patterns below were tested in Node.js unless another engine is named, and every accepted or rejected example is one we ran.

Numbers only

What you wantPatternAcceptsRejects
Digits only^[0-9]+$123, 007-42, 3.14, a leading space, empty input
Integer, optional sign^[+-]?[0-9]+$-42, +42, 0073.14, 1e3
Integer, no leading zeros^-?(?:0|[1-9][0-9]*)$0, -42007, +42
Decimal^[+-]?(?:[0-9]+(?:\.[0-9]+)?|\.[0-9]+)$3.14, -0.5, .55., 1e3, 1,000
JSON-style number^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$1.5E-7, -0.5, 01.5, +42
Thousands separators^[0-9]{1,3}(?:,[0-9]{3})*(?:\.[0-9]+)?$1,000, 12,345,678.90, 1001000, 1,00

A regex checks the shape, not the value. For a range such as 1 to 100, match the digits and then compare the number in code. Do not skip the regex and rely on conversion alone: in JavaScript Number('') is 0, Number(' 42 ') is 42, Number('0x1A') is 26, and parseInt('12abc') is 12.

Does \d only match 0 to 9?

Not everywhere. We tested ^\d+$ against the Arabic-Indic digits ٣٤:

EngineMatched non-ASCII digits?To restrict to 0–9
JavaScriptNo, even with the u flagNothing needed; \p{Nd} with u matches all decimal digits
Python 3 (str patterns)Yes, also fullwidth 12[0-9] or the re.ASCII flag
PHP (PCRE)No; yes with the u modifier[0-9]
.NETYes[0-9] or RegexOptions.ECMAScript
Perl 5Yes[0-9] or the /a modifier

That matters when a value passes the regex and then reaches code that reads it differently. Python's int('٣٤') returns 34, while a database column or another service may reject it. Write [0-9] when you mean ASCII digits; it means the same thing in every engine.

Phone numbers

The one format with a simple, international definition is E.164: a plus sign, a country code that does not start with 0, and at most 15 digits in total.

^\+[1-9]\d{1,14}$     E.164, plus sign required (best for storage)
^\+?[1-9]\d{1,14}$    plus sign optional

With the plus required, +14155552671 and +442071838750 pass, while +0123456789, a 16-digit number and 004914155552671 (written with the 00 international prefix) fail. People type spaces, dots, dashes and brackets, so strip those first with value.replace(/[\s().-]/g, ''); that turns +1 415 555 2671 into +14155552671.

The limits show up quickly. The pattern checks shape only, so +12 passes even though no real number is that short. Worse, +49 (0)30 1234567 becomes +490301234567 after cleaning and passes, but it is the wrong number: the 0 in brackets is a domestic trunk prefix that must be dropped when dialling from abroad.

National formats are no better. The common US pattern ^\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$ accepted (415-555-2671 with an unclosed bracket and 000-000-0000, which cannot be a real number. Every country has its own lengths, prefixes and mobile ranges, and they change. For real validation and formatting, use Google's libphonenumber or one of its ports: libphonenumber-js for JavaScript, phonenumbers for Python, giggsey/libphonenumber-for-php for PHP. Store the E.164 result, and confirm ownership with an SMS code if it matters.

IPv4 addresses

The popular shortcut ^\d{1,3}(?:\.\d{1,3}){3}$ accepted 256.1.1.1 and 999.999.999.999. To limit each part to 0–255, spell out the ranges:

^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$

We checked the octet part against every number from 0 to 999 written with one, two and three digits, and the full pattern against 200,000 random addresses; it agreed with Node's net.isIPv4() every time. It accepts 0.0.0.0, 192.168.1.1 and 255.255.255.255, and rejects 192.168.1, 192.168.1.1.1 and leading zeros such as 192.168.01.1.

Rejecting leading zeros is deliberate. Some parsers read them as octal: Python's socket.inet_aton('010.0.0.1') gave us the address 8.0.0.1. Node's net.isIPv4(), Python's ipaddress.ip_address() and PHP's filter_var($ip, FILTER_VALIDATE_IP) all rejected 192.168.01.1 too.

To find addresses inside a log line, drop ^ and $, add \b on both sides and use the g flag. On from 10.0.0.5 to 192.168.1.300 via 8.8.8.8 that returned 10.0.0.5 and 8.8.8.8. Without \b, the text 1192.168.1.300x produced a false match of 192.168.1.30. One limitation remains: in 1.2.3.4.5, the \b version still matches 1.2.3.4.

IPv6 addresses: use a parser

IPv6 allows :: to replace one run of zero groups, optional leading zeros, embedded IPv4 such as ::ffff:192.0.2.1 and zone IDs such as fe80::1%eth0. A regex for all of that is long and easy to get subtly wrong. A parser is shorter and better tested:

  • Node.js: net.isIPv6(value), or net.isIP(value), which returns 4, 6 or 0. Both accepted embedded IPv4 and zone IDs.
  • Python: ipaddress.ip_address(value) raises ValueError when invalid. It accepted embedded IPv4 and zone IDs.
  • PHP: filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6). It accepted embedded IPv4 but rejected fe80::1%eth0.
  • Browsers: there is no built-in IP validator, so use a library such as ipaddr.js.

If you still need a regex, for example in a schema or configuration file that only accepts one, this 408-character pattern covers the hexadecimal forms and deliberately rejects embedded IPv4 and zone IDs. On 500,000 generated and mutated strings without dots or percent signs, it agreed with net.isIPv6() on every one.

^(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:))$

Mistakes that let bad input through

  • Missing anchors. /\d+/.test('abc123') is true because it finds digits anywhere. Validation needs ^ and $.
  • Alternation outside a group. ^cat|dog$ means "starts with cat, or ends with dog", so it matched cats and dogs!. Write ^(?:cat|dog)$.
  • The m flag. With it, ^ and $ match at every line, so /^\d+$/m accepted the two-line string "abc\n123". Never use m when validating a single value.
  • $ before a final newline. In Python, PHP and .NET, ^\d+$ accepted "42\n". Use re.fullmatch() in Python, the D modifier in PHP, or \z in PHP and .NET. JavaScript rejected it.
  • The g flag with test(). A regex with g remembers lastIndex, so calling /^\d+$/g.test('42') three times on the same object returned true, false, true.
  • Unanchored alternatives in the wrong order. Searching 255.255.255.255 with the octet written as (?:\d|1\d\d|2[0-4]\d|25[0-5]) matched only 255.255.255.2, because the engine takes the first alternative that works. Put longer alternatives first, or anchor the pattern.

Test your pattern before shipping it

Paste a pattern into the Regex Tester without the slashes, set the flags to gm and enter your samples one per line: valid ones, invalid ones and the awkward cases from this page. Each line that matches is highlighted. The tester runs your browser's JavaScript engine, so recheck the \d and $ differences above if the pattern will run in Python, PHP or .NET. Validating email addresses instead? See Regex for Email Validation.

Frequently Asked Questions

What regex allows only numbers?

Use ^[0-9]+$ for digits only, or ^[+-]?[0-9]+$ to allow a sign. Prefer [0-9] to \d, because in Python, .NET and PHP with the u modifier \d also matches digits from other scripts.

What regex validates international phone numbers?

For E.164 format, ^\+[1-9]\d{1,14}$ after removing spaces, dots, dashes and brackets. It only checks the shape, so use libphonenumber or one of its ports to check that a number is actually valid for its country.

What is the regex for a valid IPv4 address?

^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$ limits every part to 0 to 255 and rejects leading zeros. For IPv6, use a parser such as net.isIPv6 in Node.js or ipaddress in Python.

Why does my regex accept input with a newline at the end?

In Python, PHP and .NET, $ also matches just before a final newline. Use re.fullmatch in Python, the D modifier in PHP, or \z instead of $.
Try it: Regex Tester