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 }Luhn.generate('order-2026-0042')Produced by @evanion/luhn 3.0.0 at f9b1d93. Not re-executed. Run it live on the unreleased page.
The Result Object
| Field | What it holds | Example (order-2026-0042) |
|---|---|---|
phrase | The cleaned input used for the calculation. | 'order20260042' |
checksum | The computed check character. | 'l' |
filtered | The 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'
('--'); // -> nullValidating 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’sphraseincludes the check character. - Minimum Length:
isValidisfalseif the phrase contains fewer than two characters (an ID plus its check character is the minimum). - Case and Separators: Like
generate,validateignores 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'); // -> nullThis 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.