How to validate a credit card with JavaScript (Luhn algorithm)

Thiago Nunes Batista
Written by: Thiago Nunes BatistaPublished on
How to validate a credit card with JavaScript (Luhn algorithm)

You are building a checkout form and need to validate the card field before sending the data to the payment gateway. The most common and recommended approach is to use the Luhn algorithm to check whether the number has the correct mathematical format.

In this article, you will understand exactly how the algorithm works, follow a step-by-step numeric example and implement a JavaScript function ready to use in your project.

What the Luhn algorithm is

The Luhn algorithm, also called the "modulus 10 formula" or "mod-10", was created by Hans Peter Luhn in 1954 and became the industry standard for validating credit card numbers. It calculates a check digit (the last digit of the number) that catches typing errors, like swapping two digits or typing a wrong number.

The idea is simple: the card issuer (bank or card network) generates the number so that, when you apply the Luhn formula, the result is divisible by 10. If it is not, the number has a formatting error.

This runs on the client side (in the browser) before the data goes to the server. That way you avoid sending invalid requests to the payment gateway and improve the user experience with instant feedback.

The algorithm step by step with a numeric example

Let's validate the Visa test number that many payment platforms use in sandbox: 4242 4242 4242 4242. First, we remove spaces and dashes, which leaves 4242424242424242.

Now we apply the Luhn algorithm:

  1. Start from the rightmost digit (the last 2) and move left. This first digit is never doubled.
  2. Double the value of every second digit from there, that is, skip one, double the next, skip one, double the next, and so on.
  3. If doubling gives a two-digit result, add the two digits together (or subtract 9, which is equivalent).
  4. Add up all the digits (the doubled/adjusted ones and the ones left unchanged).
  5. If the total is divisible by 10, the number is valid according to the Luhn algorithm.

Hands-on example with 4242424242424242

The number has 16 digits, alternating 4 and 2. Starting from the rightmost digit (which is not doubled) and moving left, the digits in the "double" position are always the 4s, and the ones in the "don't double" position are always the 2s. That happens because the whole number follows this alternating pattern.

  • Digits that are not doubled (8 digits, all 2): 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 = 16
  • Doubled digits (8 digits, all 4): each doubled 4 becomes 8 (4 × 2 = 8, not above 9, no adjustment needed). 8 + 8 + 8 + 8 + 8 + 8 + 8 + 8 = 64

Adding the two groups: 16 + 64 = 80.

Since 80 is divisible by 10 (remainder 0), this number passes the Luhn check.

Implementation in plain JavaScript

Below is a complete function that removes spaces, dashes and other non-numeric characters, then applies the Luhn algorithm:

function validateCreditCard(number) {
  // Remove everything that is not a digit
  const digits = number.replace(/\D/g, "");

  // Cards have between 13 and 19 digits (most have 16)
  if (digits.length < 13 || digits.length > 19) {
    return false;
  }

  let sum = 0;
  let shouldDouble = false; // starts at the last digit (not doubled)

  // Walk from right to left
  for (let i = digits.length - 1; i >= 0; i--) {
    let d = parseInt(digits[i], 10);

    if (shouldDouble) {
      d *= 2;
      // If the result has two digits, add them up (or subtract 9)
      if (d > 9) {
        d -= 9;
      }
    }

    sum += d;
    shouldDouble = !shouldDouble; // alternate for the next digit
  }

  return sum % 10 === 0;
}

// Testing with the Visa test number
console.log(validateCreditCard("4242 4242 4242 4242")); // true
console.log(validateCreditCard("4242424242424242")); // true
console.log(validateCreditCard("1234 5678 1234 5678")); // false

This is the function you call in your form's onblur or onsubmit. If you want to test online before integrating it, we have a free online credit card validator that applies exactly this logic.

What this algorithm does NOT guarantee

Many people skip this disclaimer and regret it later: the Luhn algorithm only validates the FORMAT/mathematical structure of the number. It does NOT check:

  • Whether the card actually exists
  • Whether it is active or blocked
  • Whether it has available credit
  • Whether it belongs to the person filling in the form
  • Whether it has been reported as stolen or used for fraud

Only the card issuer (bank or card network) can check that, through a real payment gateway (Stripe, Pagar.me, Mercado Pago, etc.). Luhn is just a "typo filter". It keeps you from sending 1234 5678 1234 5678 to the gateway and getting a silly error that could have been caught earlier.

Don't write anything in your code suggesting that passing Luhn means "a card you can charge", because that is not true.

Common mistakes

If you have implemented card validation before, you have probably seen (or made) one of these:

Validating by digit count only

Many forms only check whether the number has 16 digits. That lets 1111111111111111 through, which has 16 digits but fails Luhn. The opposite also happens: American Express cards have 15 digits, so a "must have 16" rule breaks the flow for Amex users.

Not removing spaces/dashes before validating

The user types 4242-4242-4242-4242 or 4242 4242 4242 4242. If you don't clean that up before applying Luhn, validation fails even with the correct number. The line number.replace(/\D/g, '') solves this.

Treating Luhn as a fraud check

This one is classic: the product team asks to "check if the card is valid" and the dev implements Luhn thinking it is a fraud check, which it is not. Fraud prevention requires risk analysis, tokenization, 3D Secure and gateway rules.

Using test numbers as if they were real

The test numbers that gateways like Stripe and PayPal provide pass Luhn, but they are not real cards. They only work in a sandbox environment. Never use them in production and never try to "see if it works" on a real gateway. That violates the terms and can get your account blocked.

If you need Luhn-valid test data for your forms, our credit card generator for testing creates structurally correct (but not real) numbers for you to use in QA.

Frequently asked questions

How do I know if a card number is valid?

It depends on what you mean by "valid". If you mean correct format, use the Luhn algorithm, which is what most gateways expect on the front-end. If you mean a card that actually works, only the payment gateway (through the issuer) can answer that, after you try to charge it.

Can I test a payment form without a real card?

Yes. Use the test numbers each gateway provides (Stripe, Pagar.me, Mercado Pago, etc.). They pass Luhn and simulate scenarios like "payment approved", "insufficient funds", "3D Secure", etc. Never use other people's real cards. Use only the sandbox ones.

Is validating a credit card the same as checking whether it is active?

No. Validating (with Luhn) checks whether the number has the correct mathematical structure. Checking whether it is active, has credit or is not blocked is the bank's job, done through the gateway when you try to process the payment.

What is the Luhn algorithm?

It is a math formula (modulus 10) that calculates a check digit to catch typing errors in numbers like credit cards, phone IMEIs, etc. Hans Peter Luhn created it in 1954 and it became the industry standard.

Can I use Luhn to prevent fraud?

No. Luhn only catches typos, while fraud prevention requires behavioral analysis, address verification, 3D Secure and the gateway's risk rules.

What is the difference between "Luhn-valid number" and "card you can charge"?

"Luhn-valid number" means correct mathematical format. "Card you can charge" means the issuer confirmed the card exists, is active, has credit and is not blocked. Only the gateway, together with the issuer, can answer the second question.

Wrapping up

If you are implementing card validation in a checkout form, the Luhn algorithm is the first step, and the most important one, to avoid silly typing errors. Implement the function above, test it with your gateway's sandbox numbers and always remember: Luhn validates the format, not whether the card actually works.

Want to test your implementation before shipping to production? Try our online credit card validator. It applies exactly the same logic you will use in your code.