Skip to Content
LuhnMigrating from 2.x

Migrating from 2.x

@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.

@evanion/luhn 3.x introduces breaking changes to the default dictionary and the configuration API. Most notably, the default dictionary has shrunk from 62 characters to 36, which means codes produced by 2.x will not validate under 3.x by default.

Handling Legacy Codes

If your system has already issued codes using version 2.x, you must decide how to handle them before updating. You have three primary options:

  1. Reissue: If codes are internal or short-lived, simply reissue them using 3.x.
  2. Legacy Support: Maintain a legacy instance to validate 2.x codes.
  3. Transition Period: Validate against both 2.x and 3.x instances, and reissue the code to the 3.x format when it is first read.

The 2.x default lowercased input but used a 62-character dictionary that included both cases. You can reproduce 2.x check characters using ALTERNATING_CASE_DICTIONARY combined with lowercased input:

const = ({ : }); function (: string): string { return .(.()).; } function (: string): boolean { const = .(0, -1).(); return .( + .(-1)).; } ('order-2026-0042'); // -> 'E' ('order-2026-0042-E'); // -> true ('order-2026-0043-E'); // -> false .('order-2026-0042-E').; // -> false

API Mapping: 2.x →\rightarrow 3.x

The 2.x API relied on mutable static properties on a class. 3.x replaces this with frozen instances created via createLuhn.

2.x Call / Property3.x EquivalentNotes
Luhn.generate(input)Luhn.generate(input)Call is the same; output value is different.
Luhn.validate(input)Luhn.validate(input)Call is the same; now accepts 3.x codes.
Luhn.generate(input, true)createLuhn({ caseInsensitive: false }).generate(input)Disables input folding.
Luhn.validate(input, true)createLuhn({ caseInsensitive: false }).validate(input)Disables input folding.
class X extends Luhn { static dictionary = d }createLuhn({ dictionary: d, caseInsensitive: true })Same values as the legacy subclass.
class X extends Luhn { static sensitive = true }createLuhn({ dictionary: ALTERNATING_CASE_DICTIONARY })Reproduces case-sensitive 2.x behavior.
Luhn.dictionary = dThrows TypeError\text{Throws TypeError}The default instance is now frozen.
Luhn.sensitive!Luhn.caseInsensitiveThe flag’s meaning is now inverted.
ValidationErrorLuhnErrorBase class for EmptyInputError and InvalidDictionaryError.
validate('') →\rightarrow true→\rightarrow falseEmpty strings are no longer considered valid.
generate('') →\rightarrow {'0'}→\rightarrow EmptyInputErrorGenerating a checksum for nothing is now an error.

Why the Architecture Changed

From Mutable Statics to Frozen Instances

Version 2.x was configured through mutable statics, which meant dictionaries were never checked before use. This led to runtime crashes (e.g., TypeError: dictionary.indexOf is not a function) if a dictionary was misconfigured.

createLuhn now validates the dictionary immediately upon instance creation. Once built, the instance is frozen, ensuring that the checksum logic remains consistent.

let : unknown; try { ( as { : string }). = '0123456789'; } catch () { = ; } instanceof ; // -> true .; // -> '0123456789abcdefghijklmnopqrstuvwxyz'

The Alphabet Shrinkage

The 2.x default alphabet held case pairs (both A and a). However, the library was documented as case-insensitive. This was a contradiction: if you fold A to a, the index originally held by A becomes unreachable.

The 3.x default consists of 36 lowercase alphanumerics. This is a sound basis for a case-insensitive default. If you need the full 62-character set, use ALTERNATING_CASE_DICTIONARY.

Transitioning from Subclasses to Instances

In 2.x, you created a subclass for every extra dictionary. In 3.x, you simply create a new instance. Since createLuhn builds a lookup table on every call, you should cache these instances if they recur. A luhn dictionary chosen at runtime shows the recommended caching pattern.

Last updated on