Regex to Mask Credit Card Numbers — PCI-Safe Display
Regex to mask credit card numbers, leaving only the last four digits visible. PCI DSS–aligned redaction for logs, screenshots, and customer-service displays.
Detailed Explanation
Masking Credit Card Numbers
PCI DSS allows the first six and last four digits of a PAN (primary account number) to be displayed; everything else must be masked. For most user-facing displays, only the last four are shown.
Mask All but the Last Four
str.replace(/\b(\d[ -]*?){12}(\d{4})\b/g, "**** **** **** $2")
For a 16-digit number, this turns 4111 1111 1111 1111 into **** **** **** 1111.
Preserve Original Format (any length, any separator)
For a more flexible version that masks digits while keeping spaces and dashes intact:
function maskCard(s) {
const digits = s.replace(/\D/g, "");
if (digits.length < 12) return s;
const last4 = digits.slice(-4);
const masked = "*".repeat(digits.length - 4) + last4;
// re-insert original separators
let i = 0;
return s.replace(/\d/g, () => masked[i++]);
}
Tested Examples
| Input | Output |
|---|---|
4111111111111111 |
************1111 |
4111 1111 1111 1111 |
**** **** **** 1111 |
4111-1111-1111-1111 |
****-****-****-1111 |
378282246310005 (15-digit AmEx) |
***********0005 |
Mask Inside Larger Text
To redact card numbers found anywhere in log lines:
str.replace(
/\b(\d{4}[ -]?){3}\d{4}\b/g,
m => "*".repeat(m.length - 4) + m.slice(-4)
)
Combine with Luhn Check
To avoid masking 16-digit numbers that are not actually card numbers (order IDs, etc.), apply Luhn after the regex match and only mask if it passes.
Security Reminder
Masking is for display only. Never store unmasked PANs unless you are PCI DSS–compliant. Tokenize using a payment provider (Stripe, Braintree) and reference the token instead.
Use Case
Redacting card numbers in customer-service screen shares, masking PANs in application logs that ship to third-party log services, or preparing CSV exports for analytics teams without exposing card data.