Validate a card expiry (MM/YY)
Check a card's MM/YY expiry field for a real month, 01 through 12, followed by a two-digit year — nothing else.
Last updated
Matches
- 01/25
- 12/99
- 09/30
Doesn’t match
- 13/25
- 00/25
- 1/25
Why it’s written this way
The month is checked by an alternation: (?:0[1-9]|1[0-2]) accepts 01 through 09 via the first branch and 10 through 12 via the second — a plain \d{2} would let '00' or '13' through, and no card ever expires in either.
The slash needs no escaping in a string-built RegExp, so /\d{2} simply requires exactly two digits for the year, and the surrounding anchors stop a stray extra character after '25' from sneaking past.
Edge cases to know
- →It accepts any two-digit year, including ones already in the past — pairing the regex with an actual date comparison is still necessary to catch an expired card.
- →It only recognizes the MM/YY layout; a field that also accepts MM/YYYY or 'MM-YY' needs a different pattern entirely.
- →Century assumptions live outside the regex — '25' has to be resolved to 2025 by your own logic, which eventually needs a decision about where the rollover point sits.
Part by part
- (?:0[1-9]|1[0-2])
- The month as two branches so only 01 through 12 can match. This is why 00/26 and 13/26 are rejected.
- /
- The literal separator, matching the slash printed on the card.
- \d{2}
- A two-digit year. Note this is the year only — the pattern has no idea whether that year is in the past.
Common mistakes
^\d{2}/\d{2}$Accepts month 00 and month 99. It is the shape and nothing else, which on a payment form means the error surfaces at the processor rather than in the field where it can be fixed.
^(?:0[1-9]|1[0-2])/\d{2}$^(?:0?[1-9]|1[0-2])/\d{2}$Making the leading zero optional accepts 1/26, which is ambiguous against a two-digit month and complicates every downstream parse for no gain — cards always print the zero.
^(?:0[1-9]|1[0-2])/\d{4}$A four-digit year contradicts what is printed on the card, so users typing what they see are rejected. Expand to four digits after matching if your storage needs it.
Questions
Does it check the card has not expired?
No, and this is its most important limit. 01/20 matches perfectly. Compare against the current month after matching, remembering that a card is valid through the end of its stated month, not up to the first of it.
How do I expand a two-digit year?
Add 2000, and treat the result as valid only within a sensible window ahead — cards are rarely issued more than about ten years out. A year that lands far in the future is a typo rather than a long-dated card.
Should I accept a hyphen or space instead of the slash?
It is friendlier to normalise before validating: strip everything except digits, then check the first two are a valid month. That accepts every separator a user might type without loosening the pattern itself.
Is the expiry date a security check?
Not really. It is a data-quality check that catches typos before the transaction is attempted. The real authorisation happens at the processor, which knows things about the card that no client-side check can.