Credit card (shape-only) regex
Shape check for credit-card numbers — 13 to 19 digits with optional spaces/hyphens.
^(?:\d[ -]*?){13,19}$/What it matches
Most card brands use 13-19 digits, often grouped with spaces or hyphens. This pattern validates the shape only — it does NOT confirm the number is real. Always pair it with a Luhn-algorithm check (or just send it to your payments provider and let them validate). Better still: use a hosted form like Stripe Elements so the number never touches your servers.
Examples
Matches
4111 1111 1111 1111Stripe test Visa with space grouping.
4111-1111-1111-1111Same with hyphens.
4111111111111111No separators.
3056930902590414-digit Diners Club number.
Does not match
abcd-efgh-ijkl-mnopNon-numeric.
4111Too few digits.
411111111111111111111111Too many digits.
Edge cases & gotchas
- ACCEPTS test card numbers. Real PCI compliance happens at the payment processor, not in your regex.
- Doesn't run the Luhn checksum — `4111 1111 1111 1112` matches even though the check digit is wrong.
- Doesn't differentiate brand. Visa starts with `4`, Mastercard with `5`, AmEx with `34` or `37`, Discover with `6` — you'd need separate regexes per brand for that.
- The `[ -]*?` is non-greedy so it matches the minimum separators needed — important for shape validation but not for normalization.
In your language
// JavaScript
const re = /^(?:\d[ -]*?){13,19}$/;
const match = "input".match(re);All 13 languages (including Bash, Perl, Kotlin, Swift) available in the full toolkit Export tab.
Notes for production
- Use Stripe Elements, Checkout, or Payment Element. The card number never reaches your server, and you're out of PCI-DSS scope.
- For test purposes, Stripe documents a full list of test cards including ones that simulate declines, 3DS prompts, and disputes.
Frequently asked
Should I store credit-card numbers if my regex validates them?
No. Storing card numbers puts you in PCI-DSS scope. Use Stripe Elements (or your processor's equivalent) so the card never touches your servers. We never see card numbers at tooled.dev — Stripe Embedded Checkout collects them directly.
How do I do a Luhn check?
It's a 10-line algorithm: double every other digit from the right, sum the digits of each (treating two-digit results as their digit sum), add the un-doubled digits, modulo 10 should be 0. Or just use a Luhn library — `luhn-js` on npm.
Does this match American Express?
Yes — AmEx is 15 digits, which falls within our 13-19 range. To match AmEx specifically: `^3[47]\d{13}$` (after stripping separators).