Strip spaces and hyphens, then check the number against each network rule. Keeping the rules outside the function makes it easy to add a network without extending a chain of conditionals. Unsupported or malformed input returns null.
typescript
type CardNetwork = 'Visa' | 'Mastercard' | 'American Express' | 'Discover'
const rules: ReadonlyArray<{
network: CardNetwork
matches: (digits: string) => boolean
}> = [
{
network: 'Visa',
matches: (n) => /^4(?:[0-9]{12}|[0-9]{15}|[0-9]{18})$/.test(n),
},
{
network: 'Mastercard',
matches: (n) => {
const prefix = Number(n.slice(0, 4))
return /^[0-9]{16}$/.test(n) && (
/^5[1-5]/.test(n) || (prefix >= 2221 && prefix <= 2720)
)
},
},
{
network: 'American Express',
matches: (n) => /^3[47][0-9]{13}$/.test(n),
},
{
network: 'Discover',
matches: (n) => {
const prefix = Number(n.slice(0, 6))
return /^[0-9]{16,19}$/.test(n) && (
/^(6011|65|64[4-9])/.test(n) ||
(prefix >= 622126 && prefix <= 622925)
)
},
},
]
export function detectCardNetwork(input: string): CardNetwork | null {
const digits = input.replace(/[ -]/g, '')
if (!/^[0-9]+$/.test(digits)) return null
return rules.find((rule) => rule.matches(digits))?.network ?? null
}Use the result as a display hint, such as choosing a card icon. A format match does not check the checksum, prove that an account exists, or authorize a payment. These rules cover common formats; use your payment provider for validation and current network identification.
typescript
// Example test numbers, not real account details.
detectCardNetwork('4111 1111 1111 1111') // 'Visa'
detectCardNetwork('5555-5555-5555-4444') // 'Mastercard'
detectCardNetwork('378282246310005') // 'American Express'
detectCardNetwork('6011111111111117') // 'Discover'
detectCardNetwork('hello') // null