Quick answer
This credit card regex checks the issuer prefix and length of a card number with spaces and dashes removed: Visa starts with 4 (13, 16 or 19 digits), Mastercard with 51–55 or 2221–2720 (16), American Express with 34 or 37 (15), Discover with 6011, 644–649 or 65, Diners Club with 300–305, 36 or 38, and JCB with 3528–3589. A matching number can still be mistyped, so always run the Luhn checksum too.
- Tested examples
- Token-by-token breakdown
- Code in 5 languages
- Live tester
How to use the credit card number regex
- 1Copy the pattern with Copy pattern in the tester, or take a ready-made snippet for your language from the code section below.
- 2Check the sample lines in Test string: every line the credit card number regex accepts is highlighted, rejected lines stay plain.
- 3Paste your own values one per line to see which pass, and read Explanation for what each part of the pattern does.
- 4Open the Code tab for JavaScript, Python, Go, C#, PHP or Dart with the pattern translated and escaped.
- 5Validate again on the server: client-side checks give users quick feedback, not security.
How does the credit card number regex work?
Read it left to right. Each piece below matches one part of the input:
4\d{12}(?:\d{3}){0,2}Visa: starts with 4, 13, 16 or 19 digits.|5[1-5]\d{14}Mastercard: 51–55, 16 digits.|2(?:22[1-9]|2[3-9]\d|[3-6]\d{2}|7[01]\d|720)\d{12}Mastercard 2-series: 2221–2720, 16 digits.|3[47]\d{13}American Express: 34 or 37, 15 digits.|6(?:011|5\d{2}|4[4-9]\d)\d{12,15}Discover: 6011, 644–649 or 65, 16 to 19 digits.|3(?:0[0-5]|[68]\d)\d{11}Diners Club: 300–305, 36 or 38, 14 digits.|35(?:2[89]|[3-8]\d)\d{12,15}JCB: 3528–3589, 16 to 19 digits.^(?: … )$The whole string must be one of these: digits only, no spaces.
Which strings does the credit card number regex match?
The whole input must match, from the first character to the last. Every row below is checked against the pattern by our automated test suite before each release.
| Input | Matches? | Why |
|---|---|---|
4111111111111111 | Yes | Visa test number, 16 digits. |
4222222222222 | Yes | Visa test number, 13 digits. |
5555555555554444 | Yes | Mastercard test number. |
2223003122003222 | Yes | Mastercard 2-series test number. |
378282246310005 | Yes | American Express test number, 15 digits. |
6011111111111117 | Yes | Discover test number. |
30569309025904 | Yes | Diners Club test number, 14 digits. |
3530111333300000 | Yes | JCB test number. |
4111111111111112 | Yes | Right shape, wrong checksum: only the Luhn check catches this. |
4111 1111 1111 1111 | No | Spaces: strip them before validating. |
1234567890123456 | No | No network uses the prefix 1. |
411111111111 | No | 12 digits: too short for Visa. |
5655555555554444 | No | 56 is not a Mastercard prefix. |
37828224631000 | No | Amex prefix but only 14 digits. |
How do I add the Luhn checksum?
The Luhn algorithm catches every single-digit typo and most swapped neighbors. Run it after the regex:
function luhn(digits) {
let sum = 0;
for (let i = 0; i < digits.length; i++) {
let d = Number(digits[digits.length - 1 - i]);
if (i % 2 === 1) {
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
}
return sum % 10 === 0;
}
const digits = input.replace(/[\s-]/g, "");
const valid = CARD.test(digits) && luhn(digits);4111111111111112 in the table above has the right prefix and length, but its last digit breaks the checksum. That is exactly the kind of typo users make.
Should I validate card numbers myself at all?
Only for instant feedback. If you take payments, card data should go straight from the browser to your payment provider's hosted fields or SDK (Stripe Elements, Adyen, Braintree and others), so the number never touches your server. That keeps you in the lightest PCI DSS scope, and the provider runs the authoritative checks, including whether the card exists and has funds.
Never log card numbers, and if you must show one, mask all but the last four digits. Card networks also add new prefix ranges over time (Mastercard's 2-series arrived in 2017), so treat the brand detection as a hint for showing the right logo, not as a gate that rejects payments.
How do I use the credit card number regex in JavaScript, Python, Go, C# and PHP?
The pattern is translated for each engine rather than pasted as is: named groups, anchors, escapes, shorthand classes and flags are adjusted so each snippet behaves as closely to the JavaScript version as its engine allows. Differences that remain (Go's RE2 has no lookarounds or backreferences, for example) are called out under each one. The Code tab in the tester above also exports Dart, and updates as you edit the pattern.
JavaScript and TypeScript
const regex = /^(?:4\d{12}(?:\d{3}){0,2}|5[1-5]\d{14}|2(?:22[1-9]|2[3-9]\d|[3-6]\d{2}|7[01]\d|720)\d{12}|3[47]\d{13}|6(?:011|5\d{2}|4[4-9]\d)\d{12,15}|3(?:0[0-5]|[68]\d)\d{11}|35(?:2[89]|[3-8]\d)\d{12,15})$/;
function isValid(value) {
return regex.test(value);
}
console.log(isValid("4111111111111111"));Python
import re
pattern = re.compile(r"^(?:4[0-9]{12}(?:[0-9]{3}){0,2}|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}|4[4-9][0-9])[0-9]{12,15}|3(?:0[0-5]|[68][0-9])[0-9]{11}|35(?:2[89]|[3-8][0-9])[0-9]{12,15})\Z")
def is_valid(value: str) -> bool:
return pattern.search(value) is not None
print(is_valid("4111111111111111"))- \d and \w are written as [0-9] and [A-Za-z0-9_] because in Python they also match digits and letters from other scripts.
- JavaScript's $ only matches at the very end of the string. Python's $ also matches before a trailing newline, so the pattern uses \Z instead.
Go
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`^(?:4\d{12}(?:\d{3}){0,2}|5[1-5]\d{14}|2(?:22[1-9]|2[3-9]\d|[3-6]\d{2}|7[01]\d|720)\d{12}|3[47]\d{13}|6(?:011|5\d{2}|4[4-9]\d)\d{12,15}|3(?:0[0-5]|[68]\d)\d{11}|35(?:2[89]|[3-8]\d)\d{12,15})$`)
fmt.Println(re.MatchString("4111111111111111"))
}C#
using System;
using System.Text.RegularExpressions;
// The timeout stops a pathological input from hanging the thread (ReDoS).
var regex = new Regex(@"^(?:4[0-9]{12}(?:[0-9]{3}){0,2}|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}|4[4-9][0-9])[0-9]{12,15}|3(?:0[0-5]|[68][0-9])[0-9]{11}|35(?:2[89]|[3-8][0-9])[0-9]{12,15})\z", RegexOptions.None, TimeSpan.FromSeconds(1));
Console.WriteLine(regex.IsMatch("4111111111111111"));- \d and \w are written as [0-9] and [A-Za-z0-9_] because in .NET they also match digits and letters from other scripts.
- JavaScript's $ only matches at the very end of the string. .NET's $ also matches before a trailing newline, so the pattern uses \z instead.
PHP
<?php
$pattern = '/^(?:4\d{12}(?:\d{3}){0,2}|5[1-5]\d{14}|2(?:22[1-9]|2[3-9]\d|[3-6]\d{2}|7[01]\d|720)\d{12}|3[47]\d{13}|6(?:011|5\d{2}|4[4-9]\d)\d{12,15}|3(?:0[0-5]|[68]\d)\d{11}|35(?:2[89]|[3-8]\d)\d{12,15})$/D';
function isValid(string $value, string $pattern): bool
{
return preg_match($pattern, $value) === 1;
}
var_dump(isValid('4111111111111111', $pattern));- The D modifier makes $ match only at the very end, like JavaScript (PCRE's $ also matches before a trailing newline).
Which related regex patterns are useful?
- Regex for Numbers Only: Digits only, plus variants for signs, decimals and thousands separators.
- Regex for Phone Numbers (E.164 Format): International numbers in E.164: a plus, a country code and up to 15 digits in total.
- Regex for US ZIP Codes: Five-digit ZIP codes with an optional ZIP+4 extension, like 90210-1234.
- Regex for Strong Passwords: 12+ characters with lowercase, uppercase, a digit and a symbol, built from lookaheads.
Browse all 25 patterns in the regex pattern library, or build your own in the regex tester.
Frequently asked questions
Last updated by the Playxoft engineering team.