What this regex matches
The combined pattern recognizes the major networks by their prefix and length:
^(?:4[0-9]{12}(?:[0-9]{3})?
| 5[1-5][0-9]{14}
| 2(?:22[1-9]|2[3-9][0-9]|[3-6][0-9]{2}|7[01][0-9]|720)[0-9]{12}
| 3[47][0-9]{13}
| 6(?:011|5[0-9]{2})[0-9]{12}
| 62[0-9]{14})$
Each alternative is one network. Read them as a routing table, not as one monolithic rule.
Card networks and their prefixes
The leading digits of a card number are the Issuer Identification Number (IIN), formerly called the Bank Identification Number (BIN). The first digit alone often hints at the network; longer prefixes pin it down. The regex above encodes these rules.
| Network | Starts with | Length | Notes |
|---|---|---|---|
| Visa | 4 | 13 or 16 (19 now possible) | The (?:[0-9]{3})? allows the 16-digit form plus the older 13-digit. |
| Mastercard | 51-55 and 2221-2720 | 16 | The 2-series range was added in 2016; old regexes miss it. |
| American Express | 34, 37 | 15 | Shorter than the others; do not force 16 digits. |
| Discover | 6011, 65 | 16 | Also covers some 64x ranges in practice. |
| UnionPay | 62 | 16-19 | Mainland China; length varies. |
Length ranges: 13 to 19 digits
Card numbers are not a fixed length. The ISO/IEC 7812 standard allows 13 to 19 digits, and different networks use different lengths: Amex is 15, Visa is typically 16 (but 13 and now 19 exist), UnionPay spans 16 to 19. A regex that hardcodes {16} will reject perfectly valid cards. The pattern above uses per-network lengths precisely so it does not.
Regex only proves shape, not validity
This is the point that saves you from a real bug: a regex can confirm a string starts with 4 and has the right length for Visa. It cannot confirm the number is a genuine, issued, working card. Prefix and length are public knowledge; anyone can type sixteen digits that fit the pattern.
The property that actually separates a plausible number from a valid one is the check digit, computed by the Luhn algorithm. That is arithmetic, not pattern matching, and a regex cannot do it.
The Luhn algorithm
Luhn is a simple checksum. From the rightmost digit (the check digit), double every second digit; if doubling yields two digits, subtract 9 (or add the digits). Sum everything. A valid number makes the total a multiple of 10.
def luhn_ok(num):
digits = [int(ch) for ch in num if ch.isdigit()]
if not digits:
return False
total = 0
parity = len(digits) % 2
for i, d in enumerate(digits):
if i % 2 == parity:
d *= 2
if d > 9:
d -= 9
total += d
return total % 10 == 0
Pair this with a network regex: the regex tells you the network and expected length, Luhn tells you the check digit is correct. Together they catch typos and random guesses; neither alone does.
Common mistakes
| Wrong | Problem | Right |
|---|---|---|
^4\d{15}$ for Visa | Misses 13- and 19-digit Visa cards. | ^4[0-9]{12}(?:[0-9]{3})?$ |
| One regex for all networks, no Luhn | Passes any 16 digits that look right. | Regex for network + Luhn check. |
| Ignoring Mastercard 2-series | 2221-2720 cards rejected. | Include the 2-series branch. |
| Forcing 16 digits for Amex | All Amex (15 digits) rejected. | Use per-network length. |
| Implementing Luhn as a regex | Impossible; Luhn needs arithmetic. | Use real code, not pattern. |
Security: handle real numbers with care
Card numbers are sensitive. A few non-negotiable rules:
- Do not validate real cards in untrusted front-end code you do not control; client-side checks are for UX, never for trust.
- Never log full card numbers. Log at most the last four digits, if anything.
- Do not store PANs unless you are PCI DSS compliant. The safest card data is the data you never touch — use a payment processor's tokenization instead of handling raw numbers.
- Test with published test numbers (Visa
4111 1111 1111 1111, Mastercard5500 0000 0000 0004, Amex3782 822463 10005) and never run real cards through test builds.
Test numbers you can safely use
During development you need card-shaped data without touching real cards. The networks publish fixed test numbers that pass format and Luhn checks but are rejected by live authorization. Beyond the Visa, Mastercard, and Amex examples above, common ones include Discover 6011 1111 1111 1117 and UnionPay-style 62-prefixed numbers. Hard-code these in tests, never randomize them, and keep them out of any build that could reach a real processor. Running them in continuous integration gives you stable coverage of every network and length branch without ever moving real money.
When a regex is enough
Use the network regex for what it is good at: formatting the input as the user types, picking the correct card brand icon, and routing to the right processor. Use Luhn to reject obvious typos before you ever call a payment API. Everything beyond "looks plausible" belongs to the processor and the issuer, not your regex.