gs1-core
v0.1.0
Published
Zero-dependency library to parse, validate and generate GS1 element strings, raw barcode data and GS1 Digital Link URIs
Maintainers
Readme
gs1-core
Parse, validate and generate GS1 strings in JavaScript/TypeScript. Zero dependencies.
Supports the three ways GS1 data travels:
| Format | Example |
| --- | --- |
| Human-readable element string | (01)09506000134352(17)261231(10)ABC123 |
| Raw barcode data (GS1-128, DataMatrix, QR) | ]d201095060001343521726123110ABC123 |
| GS1 Digital Link URI | https://id.gs1.org/01/09506000134352/10/ABC123?17=261231 |
Features
- Full AI dictionary: ~180 Application Identifiers from the GS1 General Specifications, including the decimal families (
310n-369n,390n-395n) - Validation of format specs, CSET 82 / CSET 39 character sets, dates, and check digits (GTIN, SSCC, GLN, GSIN, GSRN, GDTI, GCN, GRAI, ITIP)
- One
parse()call handles element strings and raw scanner data, including symbology identifiers (]C1,]d2,]Q3, ...) - Dates, weights and measures with implied decimal point, currency amounts and counts are returned as typed values, not strings
- GS1 Digital Link URIs with correct primary key and qualifier ordering
- ESM, fully typed, works in Node.js >= 18 and modern bundlers
Installation
npm install gs1-coreQuick start
import { parse } from 'gs1-core';
const elements = parse('(01)09506000134352(17)261231(3103)000189(10)ABC123');
for (const el of elements) {
console.log(el.ai, el.definition.title, el.value, el.interpreted);
}
// 01 GTIN 09506000134352
// 17 USE BY OR EXPIRY 261231 { year: 2026, month: 12, day: 31, iso: '2026-12-31' }
// 3103 NET WEIGHT (kg) 000189 0.189
// 10 BATCH/LOT ABC123Usage
Parsing scanner output
Raw data uses ASCII GS (\x1D) as the FNC1 separator by default, and a leading symbology identifier is skipped automatically:
import { parseRawString, toRecord } from 'gs1-core';
const elements = parseRawString(']C101095060001343521726123110ABC123\x1D21SER99');
toRecord(elements);
// { '01': '09506000134352', '17': '261231', '10': 'ABC123', '21': 'SER99' }
// Some scanners are configured to substitute FNC1 with another character:
parseRawString('10LOT1|21SER1', { fnc1: '|' });Parsing validates by default. Unknown AIs, wrong lengths, illegal characters, bad check digits and impossible dates all throw a GS1Error that tells you which AI failed. Pass { validate: false } to parse structure only.
import { GS1Error, parse } from 'gs1-core';
try {
parse('(01)09506000134353'); // wrong check digit
} catch (err) {
if (err instanceof GS1Error) console.error(err.ai, err.message);
// '01' 'AI (01): invalid check digit in "09506000134353" (position 14)'
}Generating strings
import { generateElementString, generateRawString } from 'gs1-core';
const items = [
{ ai: '01', value: '09506000134352' },
{ ai: '10', value: 'ABC123' },
{ ai: '17', value: '261231' },
];
generateElementString(items);
// '(01)09506000134352(10)ABC123(17)261231'
generateRawString(items);
// '010950600013435217261231' + '10ABC123'generateRawString produces the content to encode after the leading FNC1 of a GS1-128 / GS1 DataMatrix / GS1 QR symbol. Predefined-length AIs are moved to the front so fewer separators are needed (disable with { optimize: false }), and variable-length values are GS-terminated only when another element follows.
Values are validated before generation, so you cannot emit an invalid GS1 string. Check digit helpers are available if you need to compute one first:
import { withCheckDigit, computeCheckDigit, verifyCheckDigit } from 'gs1-core';
withCheckDigit('0950600013435'); // '09506000134352'
computeCheckDigit('0950600013435'); // 2
verifyCheckDigit('09506000134352'); // trueGS1 Digital Link
import { generateDigitalLink, parseDigitalLink } from 'gs1-core';
generateDigitalLink(
[
{ ai: '01', value: '09506000134352' },
{ ai: '10', value: 'ABC123' },
{ ai: '17', value: '261231' },
],
{ domain: 'https://id.example.com' }, // default: https://id.gs1.org
);
// 'https://id.example.com/01/09506000134352/10/ABC123?17=261231'
parseDigitalLink('https://id.gs1.org/01/4006381333931?17=261231');
// GTINs are normalised to 14 digits, resolver path prefixes are handled,
// and non-AI query parameters (linkType, ...) are ignored.AI dictionary
import { AI_DEFINITIONS, getAIDefinition } from 'gs1-core';
getAIDefinition('3103');
// {
// ai: '310n', title: 'NET WEIGHT (kg)', spec: 'N6',
// predefinedLength: true, fixedLength: 6, kind: 'decimal', ...
// }
for (const def of AI_DEFINITIONS.values()) {
console.log(def.ai, def.spec, def.title);
}Interpreted values
element.interpreted carries a typed value when the AI has a known semantic:
| Kind | AIs (examples) | Type |
| --- | --- | --- |
| date | 11, 13, 15, 17, 7006, ... | { year, month, day, iso }. DD = 00 resolves to the last day of the month; 2-digit years follow the GS1 -49/+50 sliding window |
| datetime | 7003, 7011, 8008, 4324, 4325 | { year, ..., hour, minute?, second?, iso } |
| dateRange | 7007 | { start, end? } |
| decimal | 310n-369n, 390n, 392n, 394n, 395n | number (implied decimal point from the 4th AI digit) |
| currencyDecimal | 391n, 393n | { currency, amount } (ISO 4217 numeric code) |
| count | 30, 37, 8111, ... | number |
API
| Function | Description |
| --- | --- |
| parse(input, options?) | Parse any GS1 string, auto-detecting the format |
| parseElementString(input, options?) | Parse (01)...(10)... element strings |
| parseRawString(input, options?) | Parse raw barcode data (fnc1 option for custom separators) |
| parseDigitalLink(uri, options?) | Parse a GS1 Digital Link URI |
| generateElementString(input, options?) | Build a human-readable element string |
| generateRawString(input, options?) | Build raw barcode content (fnc1, optimize options) |
| generateDigitalLink(input, options?) | Build a Digital Link URI (domain option) |
| computeCheckDigit(digits) / verifyCheckDigit(digits) / withCheckDigit(digits) | GS1 mod-10 check digit helpers |
| getAIDefinition(ai) / AI_DEFINITIONS | AI dictionary lookup / full table |
| toRecord(elements) | Convert parsed elements to { ai: value } |
| parseDate6, parseDateTime, resolveYear, interpretValue | Low-level interpretation helpers |
Generator functions accept either an array of { ai, value } pairs (order preserved) or a plain record { '01': '...', '10': '...' }. Note that JavaScript iterates integer-like keys such as '10' before keys like '01', so use the array form when order matters.
All errors are instances of GS1Error, which exposes the offending ai when known.
Known limitations
- In human-readable element strings, a value that itself contains a
"(AI)"-shaped sequence (parentheses are legal CSET 82 characters) is ambiguous and will be split. Use the raw format for such data; this ambiguity is inherent to the human-readable notation. - Trailing components documented as mandatory by GS1 for a few AIs (e.g. the postal code of AI 421) are treated as optional.
Contributing
Issues and pull requests are welcome.
git clone https://github.com/rmingon/gs1-core.git
cd gs1-core
npm install
npm test # run the vitest suite
npm run build # type-check and emit dist/If you spot a missing or outdated Application Identifier, please open an issue with a reference to the relevant GS1 General Specifications section. The whole dictionary lives in src/ai.ts.
