Securitycredit card regex

Credit card (shape-only) regex

Shape check for credit-card numbers — 13 to 19 digits with optional spaces/hyphens.

Pattern
/^(?:\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 1111

    Stripe test Visa with space grouping.

  • 4111-1111-1111-1111

    Same with hyphens.

  • 4111111111111111

    No separators.

  • 30569309025904

    14-digit Diners Club number.

Does not match

  • abcd-efgh-ijkl-mnop

    Non-numeric.

  • 4111

    Too few digits.

  • 411111111111111111111111

    Too 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).

Related patterns