Regex for Credit Card Numbers
Card numbers are 13 to 19 digits, usually written in groups separated by spaces or hyphens. This pattern accepts that shape and nothing else. A regex can't tell whether a number is valid — follow it with the Luhn checksum — and avoid handling raw card numbers on your own servers at all.
/^(?:\d[ -]?){12,18}\d$/How it works, token by token
| Token | Meaning |
|---|---|
| ^ | start of string |
| (?:\d[ -]?){12,18} | 12 to 18 digits, each optionally followed by one space or hyphen |
| \d | a final digit, so there's no trailing separator |
| $ | end of string |
What it matches
4111 1111 1111 11115500-0000-0000-00043400000000000094111 1111 11114111 1111 1111 11114111-1111-1111-111aTry it live
One candidate per line — the m flag makes the ^ and $ anchors apply to each line. Edit anything; it runs in your browser.
Variations
^4\d{12}(?:\d{3}){0,2}$Visa: starts with 4; 13, 16 or 19 digits (separators removed)^(?:5[1-5]\d{2}|2(?:22[1-9]|2[3-9]\d|[3-6]\d{2}|7[01]\d|720))\d{12}$Mastercard: 51–55 or 2221–2720, 16 digitsLanguage notes
- Remove separators before checking brand prefixes or running Luhn — in JavaScript, value.replace(/[ -]/g, "").
Frequently asked questions
What is the Luhn check?
A checksum built into every card number: from the right, double every second digit, subtract 9 from any result over 9, and add everything up. Valid numbers total a multiple of 10. It catches typos, not fraud.
Should my server see card numbers at all?
Avoid it. Use your payment provider's hosted fields (such as Stripe Elements), so raw numbers go straight to the provider and most PCI DSS requirements stay off your plate.
Building something custom? The regex tester gives you live match highlighting for any pattern.