@arraypress/license-keys
v1.0.0
Published
Generate and validate human-readable license keys (XXXX-XXXX-... format). Configurable alphabet, group count, and separator. Pure functions, zero dependencies.
Downloads
78
Maintainers
Readme
@arraypress/license-keys
Generate and validate human-readable license keys (XXXX-XXXX-... format). Configurable alphabet, group count, and separator. Pure functions, zero dependencies.
npm install @arraypress/license-keysQuick start
import {
generateLicenseKey,
isValidLicenseKeyFormat,
isLicenseUsable,
} from '@arraypress/license-keys';
// Default: XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX (32 hex chars)
const key = generateLicenseKey();
// → '7F3A-9C12-AB54-D9E1-2F08-7BC3-44E5-1D2A'
// Validate the shape on input
isValidLicenseKeyFormat(key); // true
isValidLicenseKeyFormat('hello'); // false
// Validate a stored license row
isLicenseUsable({ status: 'active', expires_at: '2026-12-31T23:59:59Z' });
// → { valid: true }
isLicenseUsable({ status: 'revoked', expires_at: null });
// → { valid: false, reason: 'License has been revoked' }Why
Every licensed-software product reinvents the same key-generation and validation primitives. This package is just those primitives — pure functions, no DB or storage assumptions, fully tested.
What it covers:
- Generation — random key in your chosen alphabet/shape
- Parsing — normalise customer-typed input (trim, uppercase, strip whitespace)
- Reformatting — re-apply canonical group/separator format to legacy keys
- Format validation — reject malformed input before hitting the DB
- Status + expiry checks — pure date comparison, no DB
What it deliberately doesn't cover:
- Storage / DB writes — you control the schema
- Atomic activation race-checks — those need your DB's locking, not a library
- Customer / order linkage — that's app-specific
API
generateLicenseKey(options?)
generateLicenseKey()
// → '7F3A-9C12-...-1D2A' (8 groups of 4 hex chars)
generateLicenseKey({ groups: 4, groupSize: 5, alphabet: 'unambiguous' })
// → 'A3K9P-Q2NTV-...-MR8XF' (4 groups of 5 unambiguous chars)
generateLicenseKey({ separator: '_' })
// → '7F3A_9C12_...'Options:
| Option | Default | Description |
|--------|---------|-------------|
| groups | 8 | Number of groups (1-32) |
| groupSize | 4 | Characters per group (1-16) |
| separator | '-' | String between groups |
| alphabet | 'hex' | 'hex' / 'alphanumeric' / 'unambiguous' |
Alphabets
hex(default) —0-9A-F. Familiar GUID-shaped keys.alphanumeric—0-9A-Z. Higher entropy per char, no exclusions.unambiguous— Crockford-flavoured base32. ExcludesI,L,O,U(the visually confusable glyphs). Best for keys customers type into a form.
parseLicenseKey(input)
Normalise for storage / comparison: trim, uppercase, strip whitespace. Keeps the separator.
parseLicenseKey(' 7f3a-9c12-... ');
// → '7F3A-9C12-...'stripFormatting(input, separator?)
Strip the separator and whitespace, leaving just alphabet chars. Useful for comparing against legacy unformatted storage.
stripFormatting('7F3A-9C12-...');
// → '7F3A9C12...'formatLicenseKey(input, options?)
Re-apply the canonical group/separator format to an unformatted key.
formatLicenseKey('7F3A9C12...', { groupSize: 4 });
// → '7F3A-9C12-...'isValidLicenseKeyFormat(key, options?)
Verify a key matches the configured shape. Pass alphabet-matching options if you've moved off the default hex shape.
isValidLicenseKeyFormat('7F3A-9C12-...'); // true
isValidLicenseKeyFormat('A3K9P-Q2NTV-...', { groupSize: 5 }); // true
isValidLicenseKeyFormat('not-a-key'); // falseisLicenseExpired(expiresAt, now?)
Has a license expired? Accepts ISO 8601 strings or Date objects. Naive timestamps without Z are treated as UTC (matches SQLite / D1 default storage). null / undefined / '' are treated as "no expiry" — always returns false.
isLicenseExpired(null); // false (no expiry)
isLicenseExpired('2020-01-01T00:00:00Z'); // true
isLicenseExpired('2030-01-01T00:00:00Z'); // falseisLicenseUsable(license, now?)
The composite check. Returns { valid, reason? } so the rejection message can be surfaced to the user verbatim.
isLicenseUsable(null);
// → { valid: false, reason: 'License not found' }
isLicenseUsable({ status: 'revoked', expires_at: null });
// → { valid: false, reason: 'License has been revoked' }
isLicenseUsable({ status: 'active', expires_at: '2030-01-01T00:00:00Z' });
// → { valid: true }License
MIT
