Skip to Content
LuhnGenerating and Validating

Generating and validating

@evanion/luhn 3.0.0 and its patch releases up to 3.0.2, the version on npm. Values on this page were produced by running the source at f9b1d93, a later commit than the 3.0.0 tag, and are not re-executed.

Luhn.generate computes the check character for an identifier, and Luhn.validate checks if a code carrying one is well-formed. Both return the “phrase” (the cleaned input) they read, allowing you to map a customer’s input back to a database record.

Minting a Check Character with Luhn.generate

Luhn.generate takes an identifier (without a check character) and returns the necessary data to build a valid code.

.('order-2026-0042'); // -> { phrase: 'order20260042', checksum: 'l', filtered: 2 } .('ORDER 2026/0042'); // -> { phrase: 'order20260042', checksum: 'l', filtered: 2 }
phrase
Luhn.generate('order-2026-0042')

{ phrase: 'order20260042', checksum: 'l', filtered: 2 }

Produced by @evanion/luhn 3.0.0 at f9b1d93. Not re-executed. Run it live on the unreleased page.

The Result Object

FieldWhat it holdsExample (order-2026-0042)
phraseThe cleaned input used for the calculation.'order20260042'
checksumThe computed check character.'l'
filteredThe number of characters dropped (separators, etc).2

Because generate reads the phrase, inputs like order-2026-0042 and ORDER 2026/0042 produce the same checksum. This ensures that the identity of the resource is preserved regardless of how it was written.

Handling Empty Inputs

Luhn.generate throws an EmptyInputError when no characters in the input belong to the dictionary.

If every character is dropped, the resulting phrase is empty. Since every empty phrase would produce the same check character, a blank form field or a string of hyphens ('--') would be “valid.” To prevent this, the library requires at least one valid character.

import { , } from '@evanion/luhn'; function (: string): string | null { try { return .().; } catch () { if ( instanceof ) return null; throw ; } } ('order-2026-0042'); // -> 'l' ('--'); // -> null

Validating a Code with Luhn.validate

Luhn.validate treats the last character of the input that belongs to the dictionary as the check character.

.('order-2026-0042-l'); // -> { phrase: 'order20260042l', isValid: true, filtered: 3 } .('ORDER20260042L'); // -> { phrase: 'order20260042l', isValid: true, filtered: 0 } .('order-2026-0043-l'); // -> { phrase: 'order20260043l', isValid: false, filtered: 3 } .('l'); // -> { phrase: 'l', isValid: false, filtered: 0 } .('--'); // -> { phrase: '', isValid: false, filtered: 2 }

Key Validation Rules:

  • The Phrase: validate’s phrase includes the check character.
  • Minimum Length: isValid is false if the phrase contains fewer than two characters (an ID plus its check character is the minimum).
  • Case and Separators: Like generate, validate ignores case and non-dictionary characters.

The interactive example this release showed here runs against the current source, so it is on the same page under /next/ and not on this one. Issue a code above and try changing a character or swapping two. validate will refuse them immediately.

Mapping a Code Back to an Order

To look up an order using a customer’s input, you should store the phrase generated by Luhn.generate in your database.

When a code comes back, use validate to verify it, then slice off the last character (the check character) to get the original phrase.

function (: string): string | null { const { , } = .(); return ? .(0, -1) : null; } .('order-2026-0042').; // -> 'order20260042' ('ORDER 2026 0042 L'); // -> 'order20260042' ('order-2026-0043-l'); // -> null

This approach ensures that ORDER 2026 0042 L and order-2026-0042-l both resolve to the same database key: order20260042.

Strict Input with filtered

While allowing separators is great for humans, it can be a problem for automated systems. For example, a barcode scanner should not be adding random separators.

You can use the filtered count to enforce strict input. If filtered is greater than 0, you know the input contains characters that weren’t part of the original identifier.

type = 'accepted' | 'misread' | 'unexpected characters'; function (: string): { const { , } = .(); if (!) return 'misread'; if ( > 0) return 'unexpected characters'; return 'accepted'; } ('order20260042l'); // -> 'accepted' ('order-2026-0042-l'); // -> 'unexpected characters' ('order20260043l'); // -> 'misread'

By checking filtered === 0, you can distinguish between a manually typed code (which allows separators) and a scanned code (which must be exact).

Customizing the Alphabet

By default, all calls run on a 36-character dictionary. If you need to use a different set of characters—such as the 32-character alphabet used by @evanion/token—you can build a custom instance. Dictionaries shows how to use createLuhn.

Last updated on